using AllocsFixes.JSON; using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Threading; using UnityEngine; namespace AllocsFixes.MapRendering { public class MapRendering { private static MapRendering instance; public static MapRendering Instance { get { if (instance == null) { instance = new MapRendering (); } return instance; } } private MapRenderBlockBuffer[] zoomLevelBuffers; private Dictionary dirtyChunks = new Dictionary (); private System.Timers.Timer chunkSaveTimer = new System.Timers.Timer (500); private bool renderingFullMap = false; public static bool renderingEnabled = true; private MicroStopwatch msw = new MicroStopwatch (); private MapRendering () { Constants.MAP_DIRECTORY = StaticDirectories.GetSaveGameDir () + "/map"; if (File.Exists (Constants.MAP_DIRECTORY + "/mapinfo.json")) { LoadMapInfo (); } else { WriteMapInfo (); } zoomLevelBuffers = new MapRenderBlockBuffer[Constants.ZOOMLEVELS]; for (int i = 0; i < Constants.ZOOMLEVELS; i++) { zoomLevelBuffers [i] = new MapRenderBlockBuffer (i); } chunkSaveTimer.AutoReset = false; chunkSaveTimer.Elapsed += new System.Timers.ElapsedEventHandler (TimedRendering); } public static void RenderSingleChunk (Chunk chunk) { if (renderingEnabled) { ThreadPool.QueueUserWorkItem ((o) => { try { if (!Instance.renderingFullMap) { lock (Instance.zoomLevelBuffers) { Chunk c = (Chunk)o; Vector3i cPos = c.GetWorldPos (); Vector2i cPos2 = new Vector2i (cPos.x / Constants.MAP_CHUNK_SIZE, cPos.z / Constants.MAP_CHUNK_SIZE); ushort[] mapColors = c.GetMapColors (); if (mapColors != null) { Color[] realColors = new Color[Constants.MAP_CHUNK_SIZE * Constants.MAP_CHUNK_SIZE]; for (int i_colors = 0; i_colors < mapColors.Length; i_colors++) { realColors [i_colors] = shortColorToColor (mapColors [i_colors]); } Instance.dirtyChunks [cPos2] = realColors; //Log.Out ("Add Dirty: " + cPos2); Instance.chunkSaveTimer.Stop (); Instance.chunkSaveTimer.Start (); } } } } catch (Exception e) { Log.Out ("Exception in MapRendering.RenderSingleChunk(): " + e); } }, chunk); } } public void RenderFullMap () { MicroStopwatch microStopwatch = new MicroStopwatch (); string regionSaveDir = StaticDirectories.GetSaveGameRegionDir (); RegionFileManager rfm = new RegionFileManager (regionSaveDir, regionSaveDir, 0, false); Texture2D fullMapTexture = null; Vector2i minChunk = default(Vector2i), maxChunk = default(Vector2i); Vector2i minPos = default(Vector2i), maxPos = default(Vector2i); int widthChunks, heightChunks, widthPix, heightPix; getWorldExtent (rfm, out minChunk, out maxChunk, out minPos, out maxPos, out widthChunks, out heightChunks, out widthPix, out heightPix); Log.Out (String.Format ("RenderMap: min: {0}, max: {1}, minPos: {2}, maxPos: {3}, w/h: {4}/{5}, wP/hP: {6}/{7}", minChunk.ToString (), maxChunk.ToString (), minPos.ToString (), maxPos.ToString (), widthChunks, heightChunks, widthPix, heightPix) ); lock (Instance.zoomLevelBuffers) { for (int i = 0; i < Constants.ZOOMLEVELS; i++) { zoomLevelBuffers [i].ResetBlock (); } if (Directory.Exists (Constants.MAP_DIRECTORY)) { Directory.Delete (Constants.MAP_DIRECTORY, true); } WriteMapInfo (); renderingFullMap = true; if (widthPix <= 8000 && heightPix <= 8000) fullMapTexture = new Texture2D (widthPix, heightPix); Vector2i curFullMapPos = default(Vector2i); Vector2i curChunkPos = default(Vector2i); for (curFullMapPos.x = 0; curFullMapPos.x < widthPix; curFullMapPos.x += Constants.MAP_CHUNK_SIZE) { for (curFullMapPos.y = 0; curFullMapPos.y < heightPix; curFullMapPos.y += Constants.MAP_CHUNK_SIZE) { curChunkPos.x = (curFullMapPos.x / Constants.MAP_CHUNK_SIZE) + minChunk.x; curChunkPos.y = (curFullMapPos.y / Constants.MAP_CHUNK_SIZE) + minChunk.y; try { long chunkKey = WorldChunkCache.MakeChunkKey (curChunkPos.x, curChunkPos.y); if (rfm.ContainsChunkSync (chunkKey)) { Chunk c = rfm.GetChunkSync (chunkKey); ushort[] mapColors = c.GetMapColors (); if (mapColors != null) { Color[] realColors = new Color[Constants.MAP_CHUNK_SIZE * Constants.MAP_CHUNK_SIZE]; for (int i_colors = 0; i_colors < mapColors.Length; i_colors++) { realColors [i_colors] = shortColorToColor (mapColors [i_colors]); } dirtyChunks [curChunkPos] = realColors; if (fullMapTexture != null) fullMapTexture.SetPixels (curFullMapPos.x, curFullMapPos.y, Constants.MAP_CHUNK_SIZE, Constants.MAP_CHUNK_SIZE, realColors); } } } catch (Exception e) { Log.Out ("Exception: " + e); } } while (dirtyChunks.Count > 0) { RenderDirtyChunks (); } Log.Out (String.Format ("RenderMap: {0}/{1} ({2}%)", curFullMapPos.x, widthPix, (int)((float)curFullMapPos.x / widthPix * 100))); } } if (fullMapTexture != null) { byte[] array = fullMapTexture.EncodeToPNG (); File.WriteAllBytes (Constants.MAP_DIRECTORY + "/map.png", array); Texture2D.Destroy (fullMapTexture); fullMapTexture = null; } renderingFullMap = false; Log.Out ("Generating map took: " + microStopwatch.ElapsedMilliseconds + " ms"); Log.Out ("World extent: " + minPos + " - " + maxPos); } private void SaveAllBlockMaps (object source, System.Timers.ElapsedEventArgs e) { for (int i = 0; i < Constants.ZOOMLEVELS; i++) { zoomLevelBuffers [i].SaveBlock (); } } private void TimedRendering (object source, System.Timers.ElapsedEventArgs e) { lock (zoomLevelBuffers) { RenderDirtyChunks (); if (dirtyChunks.Count > 0) Instance.chunkSaveTimer.Start (); } } private void RenderDirtyChunks () { msw.ResetAndRestart (); if (dirtyChunks.Count > 0) { List keys = new List (dirtyChunks.Keys); List chunksDone = new List (); Vector2i chunkPos = keys [0]; chunksDone.Add (chunkPos); //Log.Out ("Start Dirty: " + chunkPos); Vector2i block = default(Vector2i), blockOffset = default(Vector2i); getBlockNumber (chunkPos, out block, out blockOffset, Constants.MAP_BLOCK_TO_CHUNK_DIV, Constants.MAP_CHUNK_SIZE); zoomLevelBuffers [Constants.ZOOMLEVELS - 1].LoadBlock (block); Vector2i v_block = default(Vector2i), v_blockOffset = default(Vector2i); foreach (Vector2i v in keys) { getBlockNumber (v, out v_block, out v_blockOffset, Constants.MAP_BLOCK_TO_CHUNK_DIV, Constants.MAP_CHUNK_SIZE); if (v_block.Equals (block)) { //Log.Out ("Dirty: " + v + " render: true"); chunksDone.Add (v); zoomLevelBuffers [Constants.ZOOMLEVELS - 1].SetPart (v_blockOffset, Constants.MAP_CHUNK_SIZE, dirtyChunks [v]); } else { //Log.Out ("Dirty: " + v + " render: false"); } } foreach (Vector2i v in chunksDone) dirtyChunks.Remove (v); RenderZoomLevel (Constants.ZOOMLEVELS - 1, block); SaveAllBlockMaps (null, null); } } private void RenderZoomLevel (int level, Vector2i innerBlock) { if (level > 0) { Vector2i block = default(Vector2i), blockOffset = default(Vector2i); getBlockNumber (innerBlock, out block, out blockOffset, 2, Constants.MAP_BLOCK_SIZE / 2); zoomLevelBuffers [level - 1].LoadBlock (block); zoomLevelBuffers [level - 1].SetPart (blockOffset, Constants.MAP_BLOCK_SIZE / 2, zoomLevelBuffers [level].GetHalfScaled ()); RenderZoomLevel (level - 1, block); } } private void getBlockNumber (Vector2i innerPos, out Vector2i block, out Vector2i blockOffset, int scaleFactor, int offsetSize) { block = default(Vector2i); blockOffset = default(Vector2i); block.x = ((innerPos.x + 16777216) / scaleFactor) - (16777216 / scaleFactor); block.y = ((innerPos.y + 16777216) / scaleFactor) - (16777216 / scaleFactor); blockOffset.x = ((innerPos.x + 16777216) % scaleFactor) * offsetSize; blockOffset.y = ((innerPos.y + 16777216) % scaleFactor) * offsetSize; } private void WriteMapInfo () { JSONObject mapInfo = new JSONObject (); mapInfo.Add ("blockSize", new JSONNumber (Constants.MAP_BLOCK_SIZE)); mapInfo.Add ("maxZoom", new JSONNumber (Constants.ZOOMLEVELS - 1)); Directory.CreateDirectory (Constants.MAP_DIRECTORY); File.WriteAllText (Constants.MAP_DIRECTORY + "/mapinfo.json", mapInfo.ToString (), Encoding.UTF8); } private void LoadMapInfo () { if (File.Exists (Constants.MAP_DIRECTORY + "/mapinfo.json")) { string json = File.ReadAllText (Constants.MAP_DIRECTORY + "/mapinfo.json", Encoding.UTF8); JSONNode node = Parser.Parse (json); if (node is JSONObject) { JSONObject jo = (JSONObject)node; if (jo.ContainsKey ("blockSize")) Constants.MAP_BLOCK_SIZE = ((JSONNumber)jo ["blockSize"]).GetInt (); if (jo.ContainsKey ("maxZoom")) Constants.ZOOMLEVELS = ((JSONNumber)jo ["maxZoom"]).GetInt () + 1; } } } private void getWorldExtent (RegionFileManager rfm, out Vector2i minChunk, out Vector2i maxChunk, out Vector2i minPos, out Vector2i maxPos, out int widthChunks, out int heightChunks, out int widthPix, out int heightPix) { minChunk = default(Vector2i); maxChunk = default(Vector2i); minPos = default(Vector2i); maxPos = default(Vector2i); long[] keys = rfm.GetAllChunkKeys (); int minX = Int32.MaxValue; int minY = Int32.MaxValue; int maxX = Int32.MinValue; int maxY = Int32.MinValue; foreach (long key in keys) { int x = WorldChunkCache.extractX (key); int y = WorldChunkCache.extractZ (key); if (x < minX) minX = x; if (x > maxX) maxX = x; if (y < minY) minY = y; if (y > maxY) maxY = y; } minChunk.x = minX; minChunk.y = minY; maxChunk.x = maxX; maxChunk.y = maxY; minPos.x = minX * Constants.MAP_CHUNK_SIZE; minPos.y = minY * Constants.MAP_CHUNK_SIZE; maxPos.x = maxX * Constants.MAP_CHUNK_SIZE; maxPos.y = maxY * Constants.MAP_CHUNK_SIZE; widthChunks = maxX - minX + 1; heightChunks = maxY - minY + 1; widthPix = widthChunks * Constants.MAP_CHUNK_SIZE; heightPix = heightChunks * Constants.MAP_CHUNK_SIZE; } private static Color shortColorToColor (ushort col) { return new Color (((float)(col >> 10 & 31) / 31f), ((float)(col >> 5 & 31) / 31f), ((float)(col & 31) / 31f), 255); } } }