nearly there

map renders, viewport renders

still need to render buildings and terrain labels correctly, but it's NEARLY THERE
This commit is contained in:
Kurt 2026-01-25 13:04:45 -06:00
parent 0a80175810
commit 49067d2a2b
3 changed files with 124 additions and 63 deletions

View File

@ -140,31 +140,79 @@ public void SetAllRoad(TerrainTile tile, bool interiorOnly = true)
}
}
public int GetTileColor(int x, int y, int insideX, int insideY)
/// <inheritdoc cref="GetTileColor(ushort,int,int,int,int)"/>
public int GetTileColor(int relX, int relY, int insideX, int insideY)
{
var acre = GetAcreTemplate(x, y);
return GetTileColor(acre, x, y, insideX, insideY);
var acre = GetAcreTemplate(relX, relY);
return GetTileColor(acre, relX, relY, insideX, insideY);
}
public int GetTileColor(ushort acre, int x, int y, int insideX, int insideY)
/// <summary>
/// Gets the base acre tile color at the specified terrain coordinates.
/// </summary>
/// <remarks>
/// If the acre has a predefined appearance, that is used; otherwise, the terrain-based appearance is used.
/// </remarks>
/// <param name="acre">Base acre underneath the terrain tile.</param>
/// <param name="relX">Relative X coordinate in terrain tiles.</param>
/// <param name="relY">Relative Y coordinate in terrain tiles.</param>
/// <param name="insideX">Inside X coordinate of the terrain tile (16px max).</param>
/// <param name="insideY">Inside Y coordinate of the terrain tile (16px max).</param>
/// <returns>ARGB color value.</returns>
public int GetTileColor(ushort acre, int relX, int relY, int insideX, int insideY)
{
if (acre != 0) // predefined appearance
{
var c = AcreTileColor.GetAcreTileColor(acre, x % 16, y % 16);
var c = AcreTileColor.GetAcreTileColor(acre, relX % 16, relY % 16);
if (c != -0x1000000) // transparent
return c;
}
// dynamic (terrain-based) appearance
var tile = GetTile(x, y);
var tile = GetTile(relX, relY);
return TerrainTileColor.GetTileColor(tile, insideX, insideY).ToArgb();
}
public ushort GetAcreTemplate(int terrainX, int terrainY)
/// <summary>
/// Gets the base acre tile color at the specified terrain coordinates.
/// </summary>
/// <remarks>
/// If the acre has a predefined appearance, that is used; otherwise, the terrain-based appearance is used.
/// </remarks>
/// <param name="acre">Base acre underneath the terrain tile.</param>
/// <param name="tile">Terrain tile to render.</param>
/// <param name="relX">Relative X coordinate in terrain tiles.</param>
/// <param name="relY">Relative Y coordinate in terrain tiles.</param>
/// <param name="insideX">Inside X coordinate of the terrain tile (16px max).</param>
/// <param name="insideY">Inside Y coordinate of the terrain tile (16px max).</param>
/// <returns>ARGB color value.</returns>
public int GetTileColor(ushort acre, TerrainTile tile, int relX, int relY, int insideX, int insideY)
{
// If acre is entirely transparent (interior acre), dynamic (terrain-based) appearance
if (acre == 0)
return TerrainTileColor.GetTileColor(tile, insideX, insideY).ToArgb();
// For beaches, a slim edge is customizable (indicative by a transparent value).
// Check if pre-defined appearance governs
var color = AcreTileColor.GetAcreTileColor(acre, relX % 16, relY % 16);
if (color == -0x1000000) // transparent (dynamic)
return TerrainTileColor.GetTileColor(tile, insideX, insideY).ToArgb();
return color; // pre-defined appearance
}
/// <summary>
/// Gets the base acre template at the specified terrain coordinates.
/// </summary>
/// <param name="relX">Relative X coordinate in terrain tiles.</param>
/// <param name="relY">Relative Y coordinate in terrain tiles.</param>
/// <returns>Base acre underneath the terrain tile.</returns>
public ushort GetAcreTemplate(int relX, int relY)
{
// Acres are 16x16 tiles, and the acre data has a 1-acre deep-sea border around it.
var acreX = 1 + (terrainX / 16);
var acreY = 1 + (terrainY / 16);
var acreX = 1 + (relX / 16);
var acreY = 1 + (relY / 16);
var acreIndex = ((CountAcreWidth + 2) * acreY) + acreX;
var ofs = acreIndex * 2;

View File

@ -60,9 +60,10 @@ public MapRenderer(MapEditor m)
MapItemsReticleImage = new Bitmap(mapW, mapH);
MapItemsReticleX = new int[mapW * mapH];
MapTerrain1 = new int[MapItemsReticleX.Length / (MapScale * MapScale)];
MapTerrainX = new int[MapItemsReticleX.Length];
MapTerrain1 = new int[MapItemsReticleX.Length / (2 * 2)]; // 32px => 16px basis
MapTerrainX = new int[MapItemsReticleX.Length]; // 2x upscale
MapTerrainImage = new Bitmap(MapItemsReticleImage.Width, MapItemsReticleImage.Height);
MapTerrain1.AsSpan().Fill(TerrainSprite.ColorOcean); // blue color for ocean
// Render a single acre viewport
var tpa = cfg.TilesPerAcre;

View File

@ -18,6 +18,10 @@ public static class TerrainSprite
private static readonly Color PlazaColor = Color.RosyBrown;
private static readonly StringFormat BuildingTextFormat = new() { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center };
public const int ColorOcean = unchecked((int)0xFF80D7C3);
private const int ColorGrid1 = unchecked((int)0xFF888888u); // lighter
private const int ColorGrid2 = unchecked((int)0xFF666666u); // darker
// 6x5 in a 16x16 acre scale. Since we are in 32x32 scale for items, our upscale is "double".
// Tiles are always rendered as 16x16 squares, to match the precomputed tile appearance bitmaps.
private const int TileScale = 16;
@ -28,34 +32,26 @@ public static class TerrainSprite
private const int PlazaWidth = 6 * Scale;
private const int PlazaHeight = 5 * Scale;
public static void GenerateMap(Bitmap map, MapMutator mut, Span<int> scale1, Span<int> scaleX, int imgScale, int acreIndex = -1)
public static void GenerateMap(Bitmap map, MapMutator mut, Span<int> scale1, Span<int> scaleX, int imgScale)
{
// Load the terrain pixels, then upscale.
var mgr = mut.Manager.LayerTerrain;
LoadTerrainPixels(mgr, scale1);
LoadTerrainPixels(mgr, mut.Manager.ConfigTerrain, scale1);
ImageUtil.ScalePixelImage(scale1, scaleX, map.Width, map.Height, imgScale);
map.SetBitmapData(scaleX);
if (acreIndex < 0)
return;
var acre = AcreCoordinate.Acres[acreIndex];
var x = acre.X * mgr.TileInfo.ViewWidth;
var y = acre.Y * mgr.TileInfo.ViewHeight;
DrawReticle(map, mgr.TileInfo, x, y, imgScale);
}
public static Bitmap GetMapWithBuildings(Bitmap map, MapEditor m, Font? f,
Span<int> scale1, Span<int> scaleX,
int buildingIndex = -1)
{
GenerateMap(map, m.Mutator, scale1, scaleX, m.MapScale);
var imgScale = m.MapScale * 2; // because terrain is 16px per tile, items are 32px per tile
GenerateMap(map, m.Mutator, scale1, scaleX, imgScale);
using var gfx = Graphics.FromImage(map);
var plaza = m.Mutator.Manager.Plaza;
gfx.DrawPlaza(m, (ushort)plaza.X, (ushort)plaza.Z, m.MapScale);
gfx.DrawBuildings(m, m.Buildings.Buildings, m.MapScale, f, buildingIndex);
gfx.DrawPlaza(m, (ushort)plaza.X, (ushort)plaza.Z, imgScale);
gfx.DrawBuildings(m.Buildings.Buildings, imgScale, f, buildingIndex);
return map;
}
@ -81,10 +77,8 @@ public static void GenerateMap(Bitmap map, MapMutator mut, Span<int> scale1, Spa
img.GetBitmapData(scaleX);
// Apply Grid
const int grid1 = unchecked((int)0xFF888888u); // lighter
const int grid2 = unchecked((int)0xFF666666u); // darker
ItemLayerSprite.DrawGrid(scaleX, img.Width, img.Height, grid1, m.ViewScale); // minor
ItemLayerSprite.DrawGrid(scaleX, img.Width, img.Height, grid2, m.ViewScale * 2); // major
ItemLayerSprite.DrawGrid(scaleX, img.Width, img.Height, ColorGrid1, m.ViewScale); // minor
ItemLayerSprite.DrawGrid(scaleX, img.Width, img.Height, ColorGrid2, m.ViewScale * 2); // major
// Switch back to graphics mode
img.SetBitmapData(scaleX);
@ -124,44 +118,46 @@ private static void DrawViewBuildings(this Graphics gfx, MapEditor m, int select
var orig = ((SolidBrush)pen).Color;
pen = new SolidBrush(Color.FromArgb(transBuild, orig));
}
gfx.DrawBuilding(b, m, pen, m.ViewScale, Text);
gfx.DrawBuilding(b, pen, m.ViewScale, Text);
}
}
private static void LoadTerrainPixels(LayerTerrain mgr, Span<int> pixels)
private static void LoadTerrainPixels(LayerTerrain mgr, LayerPositionConfig cfg, Span<int> pixels)
{
var (shiftX, shiftY) = cfg.GetCoordinatesAbsolute(0, 0);
// Iterate through the relative positions within the layer.
// Then, map to absolute positions in the bitmap with the configured shift.
var width = cfg.LayerTotalWidth;
var height = cfg.LayerTotalHeight;
var mapWidth = cfg.MapTotalWidth; // 1px scale
// Populate the image, with each pixel being a single tile.
var width = mgr.TileInfo.TotalWidth;
var height = mgr.TileInfo.TotalHeight;
var i = 0;
// Only need to render the layer's width/height, as the rest is ocean/unable to be changed.
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++, i++)
pixels[i] = mgr.GetTileColor(x, y, x, y);
var absY = y + shiftY;
for (int x = 0; x < width; x++)
{
var absX = x + shiftX;
var color = mgr.GetTileColor(x, y, 0, 0);
var offset = (absY * mapWidth) + absX;
pixels[offset] = color;
}
}
}
private static void DrawReticle(Bitmap map, TileGridViewport mgr, int x, int y, int scale)
private static void DrawPlaza(this Graphics gfx, MapEditor map, ushort px, ushort py, int imgScale)
{
using var gfx = Graphics.FromImage(map);
using var pen = new Pen(Color.Red);
var (x, y) = (px * imgScale, py * imgScale);
int w = mgr.ViewWidth * scale;
int h = mgr.ViewHeight * scale;
gfx.DrawRectangle(pen, x * scale, y * scale, w, h);
}
private static void DrawPlaza(this Graphics gfx, MapEditor map, ushort px, ushort py, int scale)
{
var (x, y) = map.GetViewCoordinatesBuilding(px, py);
int width = scale * PlazaWidth;
int height = scale * PlazaHeight;
int width = imgScale * PlazaWidth;
int height = imgScale * PlazaHeight;
gfx.FillRectangle(Plaza, x, y, width, height);
}
private static void DrawBuildings(this Graphics gfx, MapEditor map, IReadOnlyList<Building> buildings, int imgScale, Font? textFont = null, int selectedBuildingIndex = -1)
private static void DrawBuildings(this Graphics gfx, IReadOnlyList<Building> buildings, int imgScale, Font? textFont = null, int selectedBuildingIndex = -1)
{
for (int i = 0; i < buildings.Count; i++)
{
@ -170,13 +166,14 @@ private static void DrawBuildings(this Graphics gfx, MapEditor map, IReadOnlyLis
continue;
var pen = selectedBuildingIndex == i ? Selected : Others;
gfx.DrawBuilding(b, map, pen, imgScale, Text, textFont);
gfx.DrawBuilding(b, pen, imgScale, Text, textFont);
}
}
private static void DrawBuilding(this Graphics gfx, Building b, MapEditor map, Brush bBrush, int imgScale, Brush textBrush, Font? textFont = null)
private static void DrawBuilding(this Graphics gfx, Building b, Brush bBrush,
int imgScale, Brush textBrush, Font? textFont = null)
{
var (x, y) = map.GetViewCoordinatesBuilding(b.X, b.Y);
var (x, y) = (b.X * imgScale, b.Y * imgScale);
var type = b.BuildingType;
var (width, height) = type.GetDimensions();
@ -197,12 +194,11 @@ private static void DrawBuilding(this Graphics gfx, Building b, MapEditor map, B
private static void SetViewTerrainPixels(LayerTerrain t, LayerPositionConfig cfg, int relX, int relY, Span<int> data, Span<int> scaleX, int imgScale)
{
GetViewTerrain1(t, cfg, relX, relY, data);
ImageUtil.ScalePixelImage(data, scaleX, TilesPerViewport * imgScale, TilesPerViewport * imgScale, imgScale);
ImageUtil.ScalePixelImage(data, scaleX, TilesPerViewport * TileScale * imgScale, TilesPerViewport * TileScale * imgScale, imgScale);
}
private static void GetViewTerrain1(LayerTerrain t, LayerPositionConfig cfg, int relX, int relY, Span<int> data)
{
int index = 0;
for (int tileY = 0; tileY < TilesPerViewport; tileY++)
{
var actY = tileY + relY;
@ -210,15 +206,31 @@ private static void GetViewTerrain1(LayerTerrain t, LayerPositionConfig cfg, int
{
var actX = relX + x;
if (!cfg.IsCoordinateValidRelative(actX, actY))
continue;
var acreTemplate = t.GetAcreTemplate(actX, actY);
for (int pixelY = 0; pixelY < TileScale; pixelY++)
{
for (int pixelX = 0; pixelX < TileScale; pixelX++)
// Fill tile's square with a solid color.
for (int pixelY = 0; pixelY < TileScale; pixelY++)
{
data[index] = t.GetTileColor(acreTemplate, actX, actY, pixelX, pixelY);
index++;
var index = (tileY * TileScale + pixelY) * (TilesPerViewport * TileScale) + x * TileScale;
for (int pixelX = 0; pixelX < TileScale; pixelX++)
{
data[index] = ColorOcean;
index++;
}
}
}
else
{
// Fill tile's square from terrain data.
var acreTemplate = t.GetAcreTemplate(actX, actY);
var tile = t.GetTile(actX, actY);
for (int pixelY = 0; pixelY < TileScale; pixelY++)
{
var index = (tileY * TileScale + pixelY) * (TilesPerViewport * TileScale) + x * TileScale;
for (int pixelX = 0; pixelX < TileScale; pixelX++)
{
data[index] = t.GetTileColor(acreTemplate, tile, actX, actY, pixelX, pixelY);
index++;
}
}
}
}