Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Terrain image import #69

Open
wants to merge 7 commits into
base: yr
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/TSMapEditor/Config/Constants.ini
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ GameRegistryInstallPath=SOFTWARE\DawnOfTheTiberiumAge
; Specifies the file filter string used in OpenFileDialog (Windows)
; For multiple file formats, list them with : (instead of ;)
; Example config with multiple file extensions: "YR maps|*.map:*.mpr:*.yrm|All files|*.*"
OpenFileDialogFilter=TS maps|*.map|All files|*.*
OpenMapFileDialogFilter=TS maps|*.map|All files|*.*

; Should the editor consider Ares [#include] or Phobos [$Include] section?
EnableIniInclude=false
Expand Down
32 changes: 32 additions & 0 deletions src/TSMapEditor/Config/Importer/Default.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
; C&C World-Altering Editor (WAE)
; https://github.com/Rampastring/TSMapEditor

; This file contains the configuration for the Terrain from image importer

[Tiles]
; Internal name for mapping = TileID
Default=0 ; LAT
Grass=0
DarkGrass=131
RoughGrass=418
Pavement=534
Sand=493
Water=322

[Colors]
; R,G,B[,A]
Red=255,0,0
Blue=0,0,255
Green=0,255,0
DarkGreen=0,191,0
Yellow=255,255,0
Gray=127,127,127

[Mapping]
; Color = Tile
Red=RoughGrass
Blue=Water
Green=Grass
DarkGreen=DarkGrass
Yellow=Sand
Gray=Pavement
107 changes: 95 additions & 12 deletions src/TSMapEditor/Config/UI/Windows/CreateNewMapWindow.ini
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
[CreateNewMapWindow]
Width=300
$CC0=lblHeader:XNALabel
$CC1=ddTheater:XNADropDown
$CC2=lblTheater:XNALabel
$CC3=tbWidth:EditorNumberTextBox
$CC4=lblWidth:XNALabel
$CC5=tbHeight:EditorNumberTextBox
$CC6=lblHeight:XNALabel
$CC7=btnCreate:EditorButton
Width=500
$CC00=lblHeader:XNALabel
$CC01=ddTheater:XNADropDown
$CC02=lblTheater:XNALabel
$CC03=tbWidth:EditorNumberTextBox
$CC04=lblWidth:XNALabel
$CC05=tbHeight:EditorNumberTextBox
$CC06=lblHeight:XNALabel
$CC07=tbBasicHeight:EditorNumberTextBox
$CC08=lblBasicHeight:XNALabel
$CC09=tbTerrainImage:EditorTextBox
$CC10=lblTerrainImage:XNALabel
$CC11=btnSelectTerrainImage:EditorButton
$CC12=tbHeightmap:EditorTextBox
$CC13=lblHeightmap:XNALabel
$CC14=btnSelectHeightmap:EditorButton
$CC15=tbImportConfig:EditorTextBox
$CC16=lblImportConfig:XNALabel
$CC17=btnSelectImportConfig:EditorButton
$CC18=btnCreate:EditorButton
$CC19=btnApplyTerrainImageRatio:EditorButton
$Height=getBottom(btnCreate) + EMPTY_SPACE_BOTTOM


Expand Down Expand Up @@ -43,7 +55,7 @@ Text=Width:

[tbHeight]
$X=getX(ddTheater)
$Width=getWidth(ddTheater)
$Width=getWidth(ddTheater) - 100
$Y=getBottom(tbWidth) + VERTICAL_SPACING
Text=100
PreviousControl=tbWidth
Expand All @@ -53,10 +65,81 @@ $X=getX(lblTheater)
$Y=getY(tbHeight) + 1
Text=Height:

[tbBasicHeight]
$X=getX(ddTheater)
$Width=getWidth(ddTheater)
$Y=getBottom(tbHeight) + VERTICAL_SPACING
Text=0
PreviousControl=tbHeight

[lblBasicHeight]
$X=getX(lblTheater)
$Y=getY(tbBasicHeight) + 1
Text=Base height:

[tbTerrainImage]
$X=getX(ddTheater)
$Width=getWidth(ddTheater) - 100
$Y=getBottom(tbBasicHeight) + VERTICAL_SPACING
PreviousControl=tbBasicHeight

[lblTerrainImage]
$X=getX(lblTheater)
$Y=getY(tbTerrainImage) + 1
Text=Terrain map:

[btnSelectTerrainImage]
$Width=100 - HORIZONTAL_SPACING
$Height=getHeight(tbTerrainImage)
$Y=getY(tbTerrainImage)
$X=getX(tbTerrainImage) + getWidth(tbTerrainImage) + HORIZONTAL_SPACING
Text=...

[tbHeightmap]
$X=getX(ddTheater)
$Width=getWidth(ddTheater) - 100
$Y=getBottom(tbTerrainImage) + VERTICAL_SPACING
PreviousControl=tbTerrainImage

[lblHeightmap]
$X=getX(lblTheater)
$Y=getY(tbHeightmap) + 1
Text=Heightmap:

[btnSelectHeightmap]
$Width=100 - HORIZONTAL_SPACING
$Height=getHeight(tbHeightmap)
$Y=getY(tbHeightmap)
$X=getX(tbHeightmap) + getWidth(tbHeightmap) + HORIZONTAL_SPACING
Text=...

[tbImportConfig]
$X=getX(ddTheater)
$Width=getWidth(ddTheater) - 100
$Y=getBottom(tbHeightmap) + VERTICAL_SPACING
PreviousControl=tbHeightmap

[lblImportConfig]
$X=getX(lblTheater)
$Y=getY(tbImportConfig) + 1
Text=Importer config:

[btnSelectImportConfig]
$Width=100 - HORIZONTAL_SPACING
$Height=getHeight(tbImportConfig)
$Y=getY(tbImportConfig)
$X=getX(tbImportConfig) + getWidth(tbImportConfig) + HORIZONTAL_SPACING
Text=...

[btnCreate]
$Width=100
$Y=getBottom(tbHeight) + EMPTY_SPACE_TOP
$Y=getBottom(tbImportConfig) + EMPTY_SPACE_TOP
$X=horizontalCenterOnParent()
Text=Create


[btnApplyTerrainImageRatio]
$Width=100
$Height=getHeight(tbHeight)
$Y=getY(tbHeight)
$X=500 - getWidth(btnApplyTerrainImageRatio) - HORIZONTAL_SPACING
Text=Auto
8 changes: 6 additions & 2 deletions src/TSMapEditor/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public static class Constants
public static int CellSizeX = 48;
public static int CellSizeY = 24;
public static int CellHeight => CellSizeY / 2;
public static double CellWHRatio = CellSizeX / CellSizeY;
public static int TileColorBufferSize = 576;

public static int RenderPixelPadding = 50;
Expand All @@ -20,10 +21,13 @@ public static class Constants

public static string ExpectedClientExecutableName = "DTA.exe";
public static string GameRegistryInstallPath = "SOFTWARE\\DawnOfTheTiberiumAge";
public static string OpenFileDialogFilter = "TS maps|*.map|All files|*.*";

public static bool EnableIniInclude = false;
public static string OpenMapFileDialogFilter = "TS maps|*.map|All files|*.*";
public static string OpenImageFileDialogFilter = "Images|*.bmp;*.png;*.jpg;*.jpeg|All files|*.*";

public static bool EnableIniInheritance = false;
public static string OpenIniFileDialogFilter = "INI|*.ini|All files|*.*";

public static bool IntegerVariables = false;

Expand Down Expand Up @@ -107,7 +111,7 @@ public static void Init()

ExpectedClientExecutableName = constantsIni.GetStringValue(ConstantsSectionName, nameof(ExpectedClientExecutableName), ExpectedClientExecutableName);
GameRegistryInstallPath = constantsIni.GetStringValue(ConstantsSectionName, nameof(GameRegistryInstallPath), GameRegistryInstallPath);
OpenFileDialogFilter = constantsIni.GetStringValue(ConstantsSectionName, nameof(OpenFileDialogFilter), OpenFileDialogFilter);
OpenMapFileDialogFilter = constantsIni.GetStringValue(ConstantsSectionName, nameof(OpenMapFileDialogFilter), OpenMapFileDialogFilter);

EnableIniInclude = constantsIni.GetBooleanValue(ConstantsSectionName, nameof(EnableIniInclude), EnableIniInclude);
EnableIniInheritance = constantsIni.GetBooleanValue(ConstantsSectionName, nameof(EnableIniInheritance), EnableIniInheritance);
Expand Down
41 changes: 41 additions & 0 deletions src/TSMapEditor/Misc/FileSystemExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System;
using System.Windows.Forms;

namespace TSMapEditor.Misc
{
public static class FileSystemExtensions
{
public const string AllFilesFilter = "All files|*.*";

public static void OpenFile(
Action<string> onOpened,
Action onAborted = null,
string filter = AllFilesFilter,
bool checkFileExist = true,
string initialDirectory = null
) {
#if WINDOWS
using (OpenFileDialog openFileDialog = new OpenFileDialog())
{
openFileDialog.InitialDirectory = string.IsNullOrEmpty(initialDirectory) ? Environment.CurrentDirectory : initialDirectory;

openFileDialog.Filter = string.IsNullOrEmpty(filter) ? AllFilesFilter : filter;
openFileDialog.CheckFileExists = checkFileExist;
openFileDialog.RestoreDirectory = true;

switch (openFileDialog.ShowDialog())
{
case DialogResult.OK:
onOpened?.Invoke(openFileDialog.FileName);
break;
default:
onAborted?.Invoke();
break;
}
}
#else
throw new NotImplementedException($"{nameof(FileSystemExtensions)}::{nameof(OpenFile)}");
#endif
}
}
}
143 changes: 143 additions & 0 deletions src/TSMapEditor/Misc/TerrainImporter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Rampastring.Tools;
using System;
using System.Collections.Generic;
using TSMapEditor.Models;
using TSMapEditor.Rendering;

namespace TSMapEditor.Misc
{
public class TerrainImporter
{
public class Configuration
{
public Dictionary<Color, int> Mapping { get; set; }
public int Default { get; set; }

public Configuration() {}
public Configuration(string fileName)
{
var tileTypes = new Dictionary<string, int>();
var colorTypes = new Dictionary<string, Color>();

var iniFile = new IniFile(fileName);
var sTileTypes = iniFile.GetSection("Tiles");
var sColorTypes = iniFile.GetSection("Colors");
var sMapping = iniFile.GetSection("Mapping");

foreach (var tileType in sTileTypes.Keys)
tileTypes.Add(tileType.Key, int.Parse(tileType.Value));
foreach (var colorType in sColorTypes.Keys)
colorTypes.Add(colorType.Key, Helpers.ColorFromString(colorType.Value));

Mapping = new Dictionary<Color, int>();
foreach (var mapping in sMapping.Keys)
Mapping.Add(colorTypes[mapping.Key], tileTypes[mapping.Value]);

Default = tileTypes["Default"];
}

public Dictionary<Color, ITileImage> ReadTheater(ITheater theater)
{
Dictionary<Color, ITileImage> mapping = new Dictionary<Color, ITileImage>();
foreach (var pair in Mapping)
mapping.Add(pair.Key, theater.GetTile(pair.Value));
return mapping;
}
}

public Configuration Current { get; set; }

public TerrainImporter(Configuration configuration)
{
Current = configuration;
}

public static Color GetPixel(Color[] colors, int x, int y, int width)
{
return colors[x + (y * width)];
}
public static Color[] GetPixels(Texture2D texture)
{
Color[] colors1D = new Color[texture.Width * texture.Height];
texture.GetData<Color>(colors1D);
return colors1D;
}

public void Import(Map map, Texture2D terrainMap, Texture2D heightMap, byte basicLevel)
{
Dictionary<Color, ITileImage> mapping = Current.ReadTheater(map.TheaterInstance);
var defaultTileImg = map.TheaterInstance.GetTile(Current.Default);

short w = (short) map.Size.X;
short h = (short) map.Size.Y;
var tiStepX = terrainMap == null ? -1 : Math.Round((double)terrainMap.Width / w);
var tiStepY = terrainMap == null ? -1 : Math.Round((double)terrainMap.Height / h);
var hmStepX = heightMap == null ? -1 : Math.Round((double)heightMap.Width / w);
var hmStepY = heightMap == null ? -1 : Math.Round((double)heightMap.Height / h);
var terrainData = terrainMap == null ? null : GetPixels(terrainMap);
var heightData = heightMap == null ? null : GetPixels(heightMap);

var offX = 0;
var offY = ((w + h) / -2);

List<MapTile> tiles = new List<MapTile>();
var createTile = (int x, int y, int bx, int by, int otx, int oty) =>
{
var tileImg = defaultTileImg;
var height = basicLevel;
if (terrainMap != null)
{
var tiX = (int)Math.Clamp(tiStepX * (x + bx), 0, terrainMap.Width - 1);
var tiY = (int)Math.Clamp(tiStepY * (y + by), 0, terrainMap.Height - 1);
var terrainColor = GetPixel(terrainData, tiX, tiY, terrainMap.Width);
tileImg = mapping.ContainsKey(terrainColor) ? mapping[terrainColor] : defaultTileImg;
}
if (heightMap != null)
{
var hmX = (int)Math.Clamp(hmStepX * (x + bx), 0, heightMap.Width - 1);
var hmY = (int)Math.Clamp(hmStepY * (y + by), 0, heightMap.Height - 1);
var heightColor = GetPixel(heightData, hmX, hmY, heightMap.Width);
height += (byte)(((short)heightColor.R + heightColor.G + heightColor.B) / 3);
}

{
var tileX = 1 + x + y + offX;
var tileY = 1 + h + w - x + y + offY;
var tile = new MapTile();

tile.Level = height;
tile.ChangeTileIndex(tileImg.TileID, 0);

tile.X = (short)Math.Round((double)tileX + otx); tile.Y = (short)Math.Round((double)tileY + oty);
tiles.Add(tile);
}
};

for (int y = 0; y < map.Size.Y; y++)
for (int x = 0; x < map.Size.X; x++)
{
// here there is a problem
// if create tile only for (x, y) then it became like chessboard:
// line (x+1, y) will be skipped.
// the easiest way just create tile line for (x+1, y) seperately.
//
// About offset. Some experiments
// *50x*61 <=> *5x*6
// 100x122 <=> 11x11
// 150x183 <=> 18x17
// 200x244 <=> 23x22
// The conclusion:
// otx = 0 - because the y component can be found in both formulas
// oty = h / 10
var otx = 0;
var oty = -((h / 6));
createTile(x, y, 0, 0, otx, oty);
createTile(x, y, 1, 0, 1 + otx, oty);
}

map.SetTileData(tiles);
}
}
}
Loading
Loading