using System; using System.Collections.Generic; //using System.Drawing; //using System.Drawing.Imaging; //using System.IO; using System.Linq; using Hjg.Pngcs; using Vintagestory.API.Client; using Vintagestory.API.Common; using Vintagestory.API.MathTools; using Vintagestory.Common; namespace Automap { public class StandardRenderer : AChunkRenderer { public const string Name = @"Standard"; /// /// Renders shards similar to the Default VS version, plus P.O.I. markings. /// /// Client API. /// Logger. public StandardRenderer(ICoreClientAPI clientAPI, ILogger logger, bool seasonalColor) : base(clientAPI, logger, seasonalColor) { } public override void GenerateChunkPngShard(Vec2i chunkPos, IMapChunk mapChunk, ColumnMeta targetColMeta, ref ColumnsMetadata allCols, out uint pixelCount) { pixelCount = 0; BlockPos tmpPos = new BlockPos( ); Vec2i localpos = new Vec2i( ); var chunksColumn = new IWorldChunk[ClientAPI.World.BlockAccessor.MapSizeY / chunkSize]; //pre-create PNG line slices... ImageLine[ ] lines = Enumerable.Repeat(new object( ), chunkSize).Select(l => new ImageLine(this.PngWriter.ImgInfo)).ToArray( ); int topChunkY = targetColMeta.YMax / chunkSize; for (int chunkY = 0; chunkY <= topChunkY; chunkY++) { chunksColumn[chunkY] = ClientAPI.World.BlockAccessor.GetChunk(chunkPos.X, chunkY, chunkPos.Y); WorldChunk ownChunk = chunksColumn[chunkY] as WorldChunk; if (ownChunk != null) { if (ownChunk.IsPacked( )) ownChunk.Unpack( );//Gah - probably done already by chunk processor } else { Logger.Warning("CHUNK A.W.O.L. : X{0} Y{1} Z{2} - Missing slice FOR COLUMN", chunkPos.X, chunkY, chunkPos.Y); //return; } } // Prefetch map chunks, in pattern var corner_pos = new Vec2i(chunkPos.X - 1, chunkPos.Y - 1); var west_pos = new Vec2i(chunkPos.X, chunkPos.Y - 1); var north_pos = new Vec2i(chunkPos.X - 1 , chunkPos.Y); uint missingRainmap = 0, missingHeightmap = 0; ColumnMeta north, northWest, west; /* "Overlap" Heightmap for Slope (height) values; covers 1 whole + 2 chunk edges, and corner block, substitute ZERO with Average Height....better than nothing even if its wrong? */ var overlapHeightmap = new ushort[chunkSize + 1, chunkSize + 1]; //Ofset copy of Heightmap...copied to Bottom, Rightmost for (int copyX = 0; copyX < chunkSize; copyX++) { for (int copyY = 0; copyY < chunkSize; copyY++) { overlapHeightmap[copyX + 1, copyY + 1] = targetColMeta.HeightMap[copyX, copyY]; } } if (allCols.Contains(corner_pos) && allCols[corner_pos].HeightMap != null) { northWest = allCols[corner_pos]; overlapHeightmap[0, 0] = northWest.HeightMap[chunkSize - 1, chunkSize - 1]; } else { missingHeightmap++; var cornerMC = ClientAPI.World.BlockAccessor.GetMapChunk(corner_pos); if (cornerMC != null && cornerMC.RainHeightMap != null) { overlapHeightmap[0, 0] = cornerMC.RainHeight2DMap(chunkSize - 1, (chunkSize - 1)); } else missingRainmap++; } if (allCols.Contains(north_pos) && allCols[north_pos].HeightMap != null) { north = allCols[north_pos]; for (int northEdgeIndex = 0; northEdgeIndex < chunkSize; northEdgeIndex++) { overlapHeightmap[0, northEdgeIndex + 1 ] = north.HeightMap[ (chunkSize - 1), northEdgeIndex]; } } else { missingHeightmap++; var northMC = ClientAPI.World.BlockAccessor.GetMapChunk(north_pos); if (northMC != null && northMC.RainHeightMap != null) { for (int northEdgeIndex = 0; northEdgeIndex < chunkSize; northEdgeIndex++) { overlapHeightmap[0, northEdgeIndex + 1] = northMC.RainHeight2DMap((chunkSize - 1), northEdgeIndex); } } else missingRainmap++; } if (allCols.Contains(west_pos) && allCols[west_pos].HeightMap != null) { west = allCols[west_pos]; for (int westEdgeIndex = 0; westEdgeIndex < chunkSize; westEdgeIndex++) { overlapHeightmap[westEdgeIndex + 1, 0] = west.HeightMap[westEdgeIndex, chunkSize - 1]; } } else { missingHeightmap++; var westMC = ClientAPI.World.BlockAccessor.GetMapChunk(west_pos); if (westMC != null && westMC.RainHeightMap != null) { for (int westEdgeIndex = 0; westEdgeIndex < chunkSize; westEdgeIndex++) { overlapHeightmap[westEdgeIndex + 1, 0] = westMC.RainHeight2DMap(westEdgeIndex, chunkSize - 1); } } else missingRainmap++; } ushort avgOverlap_Y = ( ushort )overlapHeightmap.OfType( ).Average((ushort sel) => sel == 0 ? targetColMeta.YMax : sel); //TODO: Row - then - Column averaging at Edges? #if DEBUG var badHeightData = overlapHeightmap.OfType( ).Count((ushort arg) => arg == 0); if (badHeightData > 0) Logger.VerboseDebug("H.M Zeros# {0} , Missing Rainmaps {1} Heightmaps {2}",badHeightData ,missingRainmap, missingHeightmap); //RenderDebugBitmap(overlapHeightmap, chunkPos); #endif for (int pixelIndex = 0; pixelIndex < (chunkSize * chunkSize); pixelIndex++) { /********* PIXEL RENDERING LOOP **********/ MapUtil.PosInt2d(pixelIndex, chunkSize, localpos); int localX = localpos.X; int localZ = localpos.Y; ushort localY = targetColMeta.HeightMap[localX, localZ]; int localChunkY = localY / chunkSize; if (localChunkY >= (chunksColumn.Length)) continue;//Out of range! if (chunksColumn[localChunkY] == null) { #if DEBUG Logger.VerboseDebug("Gap in chunk-column at render time Chunk-Y:{0}", localChunkY); #endif continue; } float slopeBoost = 1f; int northH, northWestH, westH; int north_X = localX + 1; int north_Z = localZ; int west_X = localX; int west_Z = localZ + 1; int northWest_X = localX; int northWest_Z = localZ; bool edge = localX == 0 || localZ == 0; northH = Math.Sign(localY - (overlapHeightmap[north_X, north_Z] == 0 ? localY : overlapHeightmap[north_X, north_Z])); northWestH = Math.Sign(localY - (overlapHeightmap[northWest_X, northWest_Z] == 0 ? localY : overlapHeightmap[northWest_X, northWest_Z])); westH = Math.Sign(localY - (overlapHeightmap[west_X, west_Z] == 0 ? localY : overlapHeightmap[west_X, west_Z])); float slopeness = (northH + northWestH + westH); float tolerance = edge ? 2.0f : 0f; if (slopeness > tolerance) slopeBoost = 1.2f; if (slopeness < tolerance) slopeBoost = 0.8f; if (Math.Abs(slopeness) <= float.Epsilon) slopeBoost = 1.0f;//Same height //slopeBoost -= 0.15f; //Slope boost value int blockId = chunksColumn[localChunkY].MaybeBlocks[MapUtil.Index3d(localX, (localY % chunkSize), localZ, chunkSize, chunkSize)]; Block block = ClientAPI.World.Blocks[blockId]; tmpPos.Set(chunkSize * chunkPos.X + localpos.X, localY, chunkSize * chunkPos.Y + localpos.Y); int red = 0, green = 0, blue = 0; ExtractBlockColor(tmpPos, block, slopeBoost, out red, out green, out blue); //============ POI Population ================= if (BlockID_Designators.ContainsKey(blockId)) { var desig = BlockID_Designators[blockId]; if (desig.Enabled) { red = desig.OverwriteColor.R; green = desig.OverwriteColor.G; blue = desig.OverwriteColor.B; } } ImageLineHelper.SetPixel(lines[localZ], localX, red, green, blue); pixelCount++; } for (int row = 0; row < this.PngWriter.ImgInfo.Rows; row++) { this.PngWriter.WriteRow(lines[row], row); } this.PngWriter.End( ); } /* private void RenderDebugBitmap( ushort[ , ] heightmap, Vec2i chunkOrigin ) { Bitmap mapBitmap = null; Graphics gContext = null; mapBitmap = new Bitmap(33, 33, PixelFormat.Format24bppRgb); gContext = Graphics.FromImage(mapBitmap); for (int x = 0; x <= chunkSize; x++) { for (int y = 0; y <= chunkSize; y++) { ushort heightCol = heightmap[x, y]; Color color= Color.FromArgb((byte)heightCol,( byte )heightCol,( byte )heightCol); mapBitmap.SetPixel(x, y, color); } } gContext.Flush(System.Drawing.Drawing2D.FlushIntention.Sync); var fileName = Path.Combine( ClientAPI.GetOrCreateDataPath("Heightmaps"),$"offhm_{chunkOrigin.X}-{chunkOrigin.Y}.png"); mapBitmap.Save(fileName, ImageFormat.Png); } */ } }