OSDN Git Service

W.I.P. #3 This Coordinate system...tricky!
[automap/automap.git] / Automap / Automap_Internals.cs
1 using System;
2 using System.Collections.Concurrent;
3 using System.Collections.Generic;
4 using System.Drawing;
5 using System.Drawing.Imaging;
6 using System.IO;
7 using System.Linq;
8 using System.Threading;
9
10 using System.Web.UI;
11
12 using Vintagestory.API.Common;
13 using Vintagestory.API.MathTools;
14
15
16
17 namespace Automap
18 {
19         public partial class AutomapMod
20         {
21                 private Thread cartographer_thread;
22
23                 private const string _mapPath = @"Maps";
24                 private const string _chunkPath = @"Chunks";
25                 private const string _domain = @"automap";
26
27                 private ConcurrentDictionary<Vec2i, uint> columnCounter = new ConcurrentDictionary<Vec2i, uint>( );
28                 private HashSet<Vec2i> knownChunkTops = new HashSet<Vec2i>( );
29
30                 private List<PointOfInterest> POIs;
31                 private Dictionary<int, Designator> BlockID_Designators;
32
33                 private int North_mostChunk;
34                 private int East_mostChunk;
35                 private int West_mostChunk;
36                 private int South_mostChunk;
37                 private Vec2i startChunkColumn;
38                 private uint lastUpdate;
39
40                 private string path;
41                 private IAsset stylesFile;
42
43                 #region Internals
44                 private void StartAutomap( )
45                 {
46                 path = ClientAPI.GetOrCreateDataPath(_mapPath);
47                 path = ClientAPI.GetOrCreateDataPath(Path.Combine(path, "World_" + ClientAPI.World.Seed));
48
49                 stylesFile = ClientAPI.World.AssetManager.Get(new AssetLocation(_domain, "config/automap_format.css"));
50                 Logger.VerboseDebug("CSS loaded: {0} size: {1}",stylesFile.IsLoaded() ,stylesFile.ToText( ).Length);
51
52
53                 Prefill_POI_Designators( );
54                 startChunkColumn = new Vec2i((ClientAPI.World.Player.Entity.LocalPos.AsBlockPos.X / ClientAPI.World.BlockAccessor.ChunkSize), (ClientAPI.World.Player.Entity.LocalPos.AsBlockPos.Z / ClientAPI.World.BlockAccessor.ChunkSize));
55                 North_mostChunk = startChunkColumn.Y;
56                 South_mostChunk = startChunkColumn.Y;
57                 East_mostChunk = startChunkColumn.X;
58                 West_mostChunk = startChunkColumn.X;
59
60                 Logger.Notification("AUTOMAP Start {0}", startChunkColumn);
61
62                 ClientAPI.Event.ChunkDirty += ChunkAChanging;
63
64                 cartographer_thread = new Thread(Cartographer);
65                 cartographer_thread.Name = "Cartographer";
66                 cartographer_thread.Priority = ThreadPriority.Lowest;
67                 cartographer_thread.IsBackground = true;
68
69                 ClientAPI.Event.RegisterGameTickListener(AwakenCartographer, 6000);
70                 }
71
72                 private void ChunkAChanging(Vec3i chunkCoord, IWorldChunk chunk, EnumChunkDirtyReason reason)
73                 {                       
74                 Vec2i topPosition = new Vec2i(chunkCoord.X, chunkCoord.Z);
75
76                 columnCounter.AddOrUpdate(topPosition, 1, (key, colAct) => colAct + 1);         
77                 }
78
79                 private void AwakenCartographer(float delayed)
80                 {
81
82                 if (ClientAPI.IsGamePaused != false || ClientAPI.IsShuttingDown != true) {
83                 #if DEBUG
84                 Logger.VerboseDebug("Cartographer re-trigger from [{0}]", cartographer_thread.ThreadState);
85                 #endif
86
87                 if (cartographer_thread.ThreadState.HasFlag(ThreadState.Unstarted)) {
88                 cartographer_thread.Start( );
89                 }
90                 else if (cartographer_thread.ThreadState.HasFlag(ThreadState.WaitSleepJoin)) {
91                 //Time to (re)write chunk shards
92                 cartographer_thread.Interrupt( );
93                 }
94                 ClientAPI.TriggerChatMessage($"Automap {lastUpdate} changes - MAX (N:{North_mostChunk},S:{South_mostChunk},E:{East_mostChunk}, W:{West_mostChunk} - TOTAL: {knownChunkTops.Count})");
95                 }
96
97                 }
98
99
100                 private void Cartographer( )
101                 {
102         wake:
103                 Logger.VerboseDebug("Cartographer thread awoken");
104
105                 try {
106                 uint ejectedItem = 0;
107                 uint updatedChunks = 0;
108
109                 while (columnCounter.Count > 0) {
110                 var mostActiveCol = columnCounter.OrderByDescending(kvp => kvp.Value).First( );
111                 var mapChunk = ClientAPI.World.BlockAccessor.GetMapChunk(mostActiveCol.Key);
112
113
114                 if (mapChunk == null) {
115                 Logger.Warning("SKIP CHUNK: ({0}) - Map Chunk NULL!", mostActiveCol.Key);
116
117                 columnCounter.TryRemove(mostActiveCol.Key, out ejectedItem);
118                 continue;
119                 }
120
121                 string filename = $"{mostActiveCol.Key.X}_{mostActiveCol.Key.Y}.png";
122                 filename = Path.Combine(path, filename);
123
124                 uint pixels = 0;
125                 var chkImg = GenerateChunkImage(mostActiveCol.Key, mapChunk, out pixels);
126
127                 if (pixels > 0) {
128                 chkImg.Save(filename, ImageFormat.Png);
129                 #if DEBUG
130                 Logger.VerboseDebug("Wrote chunk shard: ({0}) - Edits#:{1}, Pixels#:{2}", mostActiveCol.Key, mostActiveCol.Value, pixels);
131                 #endif
132                 updatedChunks++;
133                 knownChunkTops.Add(mostActiveCol.Key);
134                 columnCounter.TryRemove(mostActiveCol.Key, out ejectedItem);
135                 }
136                 else {
137                 columnCounter.TryRemove(mostActiveCol.Key, out ejectedItem);
138                 Logger.VerboseDebug("Un-painted chunk: ({0}) ", mostActiveCol.Key);
139                 }
140
141                 }
142
143                 if (updatedChunks > 0) {
144                 lastUpdate = updatedChunks;
145                 GenerateMapHTML( );
146                 updatedChunks = 0;
147                 }
148
149                 //Then sleep until interupted again, and repeat
150
151                 Logger.VerboseDebug("Thread '{0}' about to sleep indefinitely.", Thread.CurrentThread.Name);
152
153                 Thread.Sleep(Timeout.Infinite);
154
155                 } catch (ThreadInterruptedException) {
156
157                 Logger.VerboseDebug("Thread '{0}' interupted [awoken]", Thread.CurrentThread.Name);
158                 goto wake;
159
160                 } catch (ThreadAbortException) {
161                 Logger.VerboseDebug("Thread '{0}' aborted.", Thread.CurrentThread.Name);
162
163                 } finally {
164                 Logger.VerboseDebug("Thread '{0}' executing finally block.", Thread.CurrentThread.Name);
165                 }
166                 }
167                 #endregion
168
169
170
171
172
173                 private void Prefill_POI_Designators( )
174                 {
175                 this.POIs = new List<PointOfInterest>( );
176                 this.BlockID_Designators = new Dictionary<int, Designator>( );
177
178                 //Add special marker types for BlockID's of "Interest", plus a special overwrite colour for them
179
180                 var roadIDs = Helpers.ArbitrarytBlockIdHunter(ClientAPI, new AssetLocation("game", "stonepath"), EnumBlockMaterial.Gravel);
181                 var roadDesignator = new Designator
182                                 (
183                                  Color.Yellow
184                                 );
185
186                 foreach (var entry in roadIDs) {
187                 BlockID_Designators.Add(entry.Key, roadDesignator);
188                 }
189
190
191
192                 }
193
194
195
196                 #region COPYPASTA
197                 //TODO: rewrite - with alternate algo.
198                 //A slightly re-written; ChunkMapLayer :: public int[] GenerateChunkImage(Vec2i chunkPos, IMapChunk mc)
199                 internal Bitmap GenerateChunkImage(Vec2i chunkPos, IMapChunk mc, out uint pixelCount)
200                 {
201                 pixelCount = 0;
202                 BlockPos tmpPos = new BlockPos( );
203                 Vec2i localpos = new Vec2i( );
204                 int chunkSize = ClientAPI.World.BlockAccessor.ChunkSize;
205                 var chunksColumn = new IWorldChunk[ClientAPI.World.BlockAccessor.MapSizeY / chunkSize];
206                 Bitmap chunkImage = new Bitmap(chunkSize, chunkSize, PixelFormat.Format24bppRgb);
207                 int topChunkY = mc.YMax / chunkSize;//Heywaitaminute -- this isn't a highest FEATURE, if Rainmap isn't accurate!
208                                                                                         //Metadata of DateTime chunk was edited, chunk coords.,world-seed? Y-Max feature height
209                                                                                         //Grab a chunk COLUMN... Topmost Y down...
210                 for (int chunkY = 0; chunkY <= topChunkY; chunkY++) {
211                 chunksColumn[chunkY] = ClientAPI.World.BlockAccessor.GetChunk(chunkPos.X, chunkY, chunkPos.Y);
212                 //What to do if chunk is a void? invalid?
213                 }
214
215                 // Prefetch map chunks, in pattern
216                 IMapChunk[ ] mapChunks = new IMapChunk[ ]
217                 {
218                         ClientAPI.World.BlockAccessor.GetMapChunk(chunkPos.X - 1, chunkPos.Y - 1),
219                         ClientAPI.World.BlockAccessor.GetMapChunk(chunkPos.X - 1, chunkPos.Y),
220                         ClientAPI.World.BlockAccessor.GetMapChunk(chunkPos.X, chunkPos.Y - 1)
221                 };
222
223
224                 for (int posIndex = 0; posIndex < (chunkSize * chunkSize); posIndex++) {
225                 int mapY = mc.RainHeightMap[posIndex];
226                 int localChunkY = mapY / chunkSize;
227                 if (localChunkY >= (chunksColumn.Length)) continue;//Out of range!
228
229                 MapUtil.PosInt2d(posIndex, chunkSize, localpos);
230                 int localX = localpos.X;
231                 int localZ = localpos.Y;
232
233                 float b = 1;
234                 int leftTop, rightTop, leftBot;
235
236                 IMapChunk leftTopMapChunk = mc;
237                 IMapChunk rightTopMapChunk = mc;
238                 IMapChunk leftBotMapChunk = mc;
239
240                 int topX = localX - 1;
241                 int botX = localX;
242                 int leftZ = localZ - 1;
243                 int rightZ = localZ;
244
245                 if (topX < 0 && leftZ < 0) {
246                 leftTopMapChunk = mapChunks[0];
247                 rightTopMapChunk = mapChunks[1];
248                 leftBotMapChunk = mapChunks[2];
249                 }
250                 else {
251                 if (topX < 0) {
252                 leftTopMapChunk = mapChunks[1];
253                 rightTopMapChunk = mapChunks[1];
254                 }
255                 if (leftZ < 0) {
256                 leftTopMapChunk = mapChunks[2];
257                 leftBotMapChunk = mapChunks[2];
258                 }
259                 }
260
261                 topX = GameMath.Mod(topX, chunkSize);
262                 leftZ = GameMath.Mod(leftZ, chunkSize);
263
264                 leftTop = leftTopMapChunk == null ? 0 : Math.Sign(mapY - leftTopMapChunk.RainHeightMap[leftZ * chunkSize + topX]);
265                 rightTop = rightTopMapChunk == null ? 0 : Math.Sign(mapY - rightTopMapChunk.RainHeightMap[rightZ * chunkSize + topX]);
266                 leftBot = leftBotMapChunk == null ? 0 : Math.Sign(mapY - leftBotMapChunk.RainHeightMap[leftZ * chunkSize + botX]);
267
268                 float slopeness = (leftTop + rightTop + leftBot);
269
270                 if (slopeness > 0) b = 1.2f;
271                 if (slopeness < 0) b = 0.8f;
272
273                 b -= 0.15f; //Slope boost value 
274
275                 if (chunksColumn[localChunkY] == null) {
276
277                 continue;
278                 }
279
280                 chunksColumn[localChunkY].Unpack( );
281                 int blockId = chunksColumn[localChunkY].Blocks[MapUtil.Index3d(localpos.X, mapY % chunkSize, localpos.Y, chunkSize, chunkSize)];
282
283                 Block block = ClientAPI.World.Blocks[blockId];
284
285                 tmpPos.Set(chunkSize * chunkPos.X + localpos.X, mapY, chunkSize * chunkPos.Y + localpos.Y);
286
287                 int avgCol = block.GetColor(ClientAPI, tmpPos);
288                 int rndCol = block.GetRandomColor(ClientAPI, tmpPos, BlockFacing.UP);
289                 //This is still, an abnormal color - the tint is too blue
290                 int col = ColorUtil.ColorOverlay(avgCol, rndCol, 0.125f);
291                 var packedFormat = ColorUtil.ColorMultiply3Clamped(col, b);
292
293                 Color pixelColor = Color.FromArgb(ColorUtil.ColorR(packedFormat), ColorUtil.ColorG(packedFormat), ColorUtil.ColorB(packedFormat));
294
295                 //============ POI Population =================
296                 if (BlockID_Designators.ContainsKey(blockId)) {
297                 var desig = BlockID_Designators[blockId];
298                 pixelColor = desig.OverwriteColor;
299
300                 if (desig.SpecialAction != null) {
301                 desig.SpecialAction.Invoke(tmpPos, block);
302                 }
303                 }
304
305                 chunkImage.SetPixel(localX, localZ, pixelColor);
306                 pixelCount++;
307                 }
308
309
310                 return chunkImage;
311                 }
312                 #endregion
313
314
315                 private void GenerateMapHTML( )
316                 {
317                 string mapFilename = Path.Combine(path, "Automap.html");
318
319                 North_mostChunk = knownChunkTops.Min(tc => tc.Y);
320                 South_mostChunk = knownChunkTops.Max(tc => tc.Y);
321                 East_mostChunk = knownChunkTops.Max(tc => tc.X);
322                 West_mostChunk = knownChunkTops.Min(tc => tc.X);
323
324
325                 using (StreamWriter outputText = new StreamWriter(File.Open(mapFilename, FileMode.Create, FileAccess.Write))) {
326                 using (HtmlTextWriter tableWriter = new HtmlTextWriter(outputText)) {
327                 tableWriter.BeginRender( );
328                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Html);
329
330                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Head);
331                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Title);
332                 tableWriter.WriteEncodedText("Generated Automap");
333                 tableWriter.RenderEndTag( );
334                 //CSS  style  here
335                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Style);
336                 tableWriter.Write(stylesFile.ToText( ));
337                 tableWriter.RenderEndTag( );//</style>
338
339                 tableWriter.RenderEndTag( );
340
341                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Body);
342                 tableWriter.RenderBeginTag(HtmlTextWriterTag.P);
343                 tableWriter.WriteEncodedText($"Created {DateTimeOffset.UtcNow.ToString("u")}");
344                 tableWriter.RenderEndTag( );
345                 tableWriter.RenderBeginTag(HtmlTextWriterTag.P);
346                 tableWriter.WriteEncodedText($"W:{West_mostChunk}, E: {East_mostChunk}, N:{North_mostChunk}, S:{South_mostChunk} ");
347                 tableWriter.RenderEndTag( );
348                 tableWriter.WriteLine( );
349                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Table);
350                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Caption);
351                 tableWriter.WriteEncodedText($"Start: {startChunkColumn}, Seed: {ClientAPI.World.Seed}\n");             
352                 tableWriter.RenderEndTag( );
353
354                 //################ X-Axis <thead> #######################
355                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Thead);
356                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Tr);
357
358                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Th);
359                 tableWriter.Write("N, E");
360                 tableWriter.RenderEndTag( );
361
362                 for (int xAxisT = West_mostChunk; xAxisT <= East_mostChunk; xAxisT++) {
363                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Th);
364                 tableWriter.Write(xAxisT);
365                 tableWriter.RenderEndTag( );
366                 }
367
368                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Th);
369                 tableWriter.Write("N, W");
370                 tableWriter.RenderEndTag( );
371                 
372                 tableWriter.RenderEndTag( );
373                 tableWriter.RenderEndTag( );
374                 //###### </thead> ################################
375
376                 //###### <tbody> - Chunk rows & Y-axis cols
377                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Tbody);
378                 //######## <tr> for every vertical row
379                 for (int yAxis = North_mostChunk; yAxis <= South_mostChunk; yAxis++) {
380                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Tr);
381                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Td);
382                 tableWriter.Write(yAxis);//legend: Y-axis
383                 tableWriter.RenderEndTag( );
384
385                 for (int xAxis = West_mostChunk; xAxis <= East_mostChunk; xAxis++) {
386                 //###### <td>  #### for chunk shard 
387                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Td);
388                 if (knownChunkTops.Contains( new Vec2i(xAxis, yAxis))){
389                 tableWriter.AddAttribute(HtmlTextWriterAttribute.Src, $"{xAxis}_{yAxis}.png");
390                 tableWriter.AddAttribute(HtmlTextWriterAttribute.Alt, $"X{xAxis}, Y{yAxis}");
391                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Img);
392                 tableWriter.RenderEndTag( );
393                 }
394                 else {
395                 tableWriter.Write("?");
396                 }               
397                 tableWriter.RenderEndTag( );
398                 }//############ </td> ###########
399
400                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Td);
401                 tableWriter.Write(yAxis);//legend: Y-axis
402                 tableWriter.RenderEndTag( );
403
404                 tableWriter.RenderEndTag( );
405                 tableWriter.EndRender( );
406                 tableWriter.Flush( );
407                 }
408                 tableWriter.RenderEndTag( );
409
410                 //################ X-Axis <tfoot> #######################
411                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Tfoot);
412                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Tr);
413
414                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Td);
415                 tableWriter.Write("S, E");
416                 tableWriter.RenderEndTag( );
417
418                 for (int xAxisB = West_mostChunk; xAxisB <= East_mostChunk; xAxisB++) {
419                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Td);
420                 tableWriter.Write(xAxisB);
421                 tableWriter.RenderEndTag( );
422                 }
423
424                 tableWriter.RenderBeginTag(HtmlTextWriterTag.Td);
425                 tableWriter.Write("S, W");
426                 tableWriter.RenderEndTag( );
427
428                 tableWriter.RenderEndTag( );
429                 tableWriter.RenderEndTag( );
430                 //###### </tfoot> ################################
431
432
433                 tableWriter.RenderEndTag( );//</table>
434                 tableWriter.EndRender( );
435                 tableWriter.Flush( );
436                 }
437                 outputText.Flush( );            
438                 }
439
440                 Logger.VerboseDebug("Generated HTML map");
441                 }
442
443
444
445         }
446
447 }