public static void Calculate(Level level) { var chunks = level.GetLoadedChunks().OrderBy(column => column.x).ThenBy(column => column.z); SkyLightBlockAccess blockAccess = new SkyLightBlockAccess(level.WorldProvider); _chunkCount = chunks.Count(); if (_chunkCount == 0) { return; } CheckIfSpawnIsMiddle(chunks, level.SpawnPoint.GetCoordinates3D()); Stopwatch sw = new Stopwatch(); sw.Start(); //Parallel.ForEach(chunks, chunk => chunk.RecalcHeight()); //Log.Debug($"Recalc height level {level.LevelName}({level.LevelId}) for {_chunkCount} chunks, {_chunkCount*16*16*256} blocks. Time {sw.ElapsedMilliseconds}ms"); SkyLightCalculations calculator = new SkyLightCalculations(Config.GetProperty("CalculateLights.MakeMovie", false)); int midX = calculator.GetMidX(chunks.ToArray()); //int width = calculator.GetWidth(chunks.ToArray()); sw.Restart(); HighPrecisionTimer tickerHighPrecisionTimer = null; if (calculator.TrackResults) { tickerHighPrecisionTimer = new HighPrecisionTimer(100, _ => calculator.SnapshotVisits()); } calculator.StartTimeInMilliseconds = Environment.TickCount; var t0 = Task.Run(() => { var pairs = chunks.OrderBy(pair => pair.x).ThenBy(pair => pair.z).Where(chunk => chunk.x <= midX).OrderByDescending(pair => pair.x).ThenBy(pair => pair.z).ToArray(); calculator.CalculateSkyLights(blockAccess, pairs); }); var t5 = Task.Run(() => { var pairs = chunks.OrderByDescending(pair => pair.x).ThenBy(pair => pair.z).Where(chunk => chunk.x > midX).OrderBy(pair => pair.x).ThenByDescending(pair => pair.z).ToArray(); calculator.CalculateSkyLights(blockAccess, pairs); }); var t1 = Task.Run(() => { var pairs = chunks.OrderBy(pair => pair.x).ThenBy(pair => pair.z).ToArray(); calculator.CalculateSkyLights(blockAccess, pairs); }); var t2 = Task.Run(() => { var pairs = chunks.OrderByDescending(pair => pair.x).ThenByDescending(pair => pair.z).ToArray(); calculator.CalculateSkyLights(blockAccess, pairs); }); var t3 = Task.Run(() => { var pairs = chunks.OrderByDescending(pair => pair.x).ThenBy(pair => pair.z).ToArray(); calculator.CalculateSkyLights(blockAccess, pairs); }); var t4 = Task.Run(() => { var pairs = chunks.OrderBy(pair => pair.x).ThenByDescending(pair => pair.z).ToArray(); calculator.CalculateSkyLights(blockAccess, pairs); }); Task.WaitAll(t0, t1, t2, t3, t4, t5); Log.Debug($"Recalc skylight for {_chunkCount:N0} chunks, {_chunkCount*16*16*256:N0} blocks. Touches={calculator.visits:N0} Time {sw.ElapsedMilliseconds:N0}ms"); if (calculator.TrackResults) { Task.Run(() => { tickerHighPrecisionTimer?.Dispose(); calculator.SnapshotVisits(); calculator.SnapshotVisits(); if (calculator.RenderingTasks.Count == 0) { return; } // Start with an end-frame (twitter thumbs) var last = calculator.RenderingTasks.Last(); calculator.RenderingTasks.Remove(last); calculator.RenderingTasks.Insert(0, last); calculator.RenderVideo(); Log.Debug($"Movie rendered."); }); } //foreach (var chunk in chunks) //{ // calculator.ShowHeights(chunk); //} //var chunkColumn = chunks.First(column => column.x == -1 && column.z == 0 ); //if (chunkColumn != null) //{ // Log.Debug($"Heights:\n{Package.HexDump(chunkColumn.height)}"); // Log.Debug($"skylight.Data:\n{Package.HexDump(chunkColumn.skyLight.Data, 64)}"); //} }
public ChunkColumn GetChunk(ChunkCoordinates coordinates, string basePath, IWorldGenerator generator) { try { const int width = 32; const int depth = 32; int rx = coordinates.X >> 5; int rz = coordinates.Z >> 5; string filePath = Path.Combine(basePath, string.Format(@"region{2}r.{0}.{1}.mca", rx, rz, Path.DirectorySeparatorChar)); if (!File.Exists(filePath)) { var chunkColumn = generator?.GenerateChunkColumn(coordinates); if (chunkColumn != null) { //SkyLightBlockAccess blockAccess = new SkyLightBlockAccess(this, chunkColumn); //new SkyLightCalculations().RecalcSkyLight(chunkColumn, blockAccess); } return(chunkColumn); } using (var regionFile = File.OpenRead(filePath)) { int locationIndex = ((coordinates.X & (width - 1)) + (coordinates.Z & (depth - 1)) * width) << 2; regionFile.Seek(locationIndex, SeekOrigin.Begin); byte[] offsetBuffer = new byte[4]; regionFile.Read(offsetBuffer, 0, 3); Array.Reverse(offsetBuffer); int offset = BitConverter.ToInt32(offsetBuffer, 0) << 4; int sectorCount = regionFile.ReadByte(); if (offset == 0 || sectorCount == 0) { var chunkColumn = generator?.GenerateChunkColumn(coordinates); if (chunkColumn != null) { chunkColumn.NeedSave = true; } return(chunkColumn); } /*regionFile.Seek(4096 + locationIndex, SeekOrigin.Begin); * regionFile.Read(offsetBuffer, 0, 4); * Array.Reverse(offsetBuffer); * int lastModified = BitConverter.ToInt32(offsetBuffer, 0); * Log.Warn("Last modified: " + lastModified);*/ regionFile.Seek(offset + 4, SeekOrigin.Begin); //Offset + the length header int compressionMode = regionFile.ReadByte(); if (compressionMode != 0x02) { throw new Exception($"CX={coordinates.X}, CZ={coordinates.Z}, NBT wrong compression. Expected 0x02, got 0x{compressionMode:X2}. " + $"Offset={offset}, Sectors={sectorCount}"); } var nbt = new NbtFile(); nbt.LoadFromStream(regionFile, NbtCompression.ZLib); NbtCompound dataTag = (NbtCompound)nbt.RootTag["Level"]; bool isPocketEdition = false; if (dataTag.Contains("MCPE BID")) { isPocketEdition = dataTag["MCPE BID"].ByteValue == 1; } NbtList sections = dataTag["Sections"] as NbtList; ChunkColumn chunk = new ChunkColumn { x = coordinates.X, z = coordinates.Z, biomeId = dataTag["Biomes"].ByteArrayValue, isAllAir = true }; if (chunk.biomeId.Length > 256) { throw new Exception(); } NbtTag heights = dataTag["HeightMap"] as NbtIntArray; if (heights != null) { int[] intHeights = heights.IntArrayValue; for (int i = 0; i < 256; i++) { chunk.height[i] = (short)intHeights[i]; } } // This will turn into a full chunk column foreach (NbtTag sectionTag in sections) { ReadSection(sectionTag, chunk, !isPocketEdition); } NbtList entities = dataTag["Entities"] as NbtList; if (entities != null) { chunk.Entities = entities.ToArray <NbtCompound>(); } NbtList blockEntities = dataTag["TileEntities"] as NbtList; if (blockEntities != null) { foreach (var nbtTag in blockEntities) { var blockEntityTag = (NbtCompound)nbtTag.Clone(); string entityId = blockEntityTag["id"].StringValue; int x = blockEntityTag["x"].IntValue; int y = blockEntityTag["y"].IntValue; int z = blockEntityTag["z"].IntValue; if (entityId.StartsWith("minecraft:")) { var id = entityId.Split(':')[1]; entityId = id.First().ToString().ToUpper() + id.Substring(1); blockEntityTag["id"] = new NbtString("id", entityId); } BlockEntity blockEntity = BlockEntityFactory.GetBlockEntityById(entityId); if (blockEntity != null) { blockEntityTag.Name = string.Empty; if (blockEntity is Sign) { // Remove the JSON stuff and get the text out of extra data. // TAG_String("Text2"): "{"extra":["10c a loaf!"],"text":""}" CleanSignText(blockEntityTag, "Text1"); CleanSignText(blockEntityTag, "Text2"); CleanSignText(blockEntityTag, "Text3"); CleanSignText(blockEntityTag, "Text4"); } else if (blockEntity is ChestBlockEntity) { NbtList items = (NbtList)blockEntityTag["Items"]; if (items != null) { //for (byte i = 0; i < items.Count; i++) //{ // NbtCompound item = (NbtCompound) items[i]; // item.Add(new NbtShort("OriginalDamage", item["Damage"].ShortValue)); // byte metadata = (byte) (item["Damage"].ShortValue & 0xff); // item.Remove("Damage"); // item.Add(new NbtByte("Damage", metadata)); //} } } chunk.SetBlockEntity(new BlockCoordinates(x, y, z), blockEntityTag); } else { if (Log.IsDebugEnabled) { Log.Debug($"Loaded unknown block entity: {blockEntityTag}"); } } } } //NbtList tileTicks = dataTag["TileTicks"] as NbtList; chunk.RecalcHeight(); chunk.isDirty = false; chunk.NeedSave = false; if (Config.GetProperty("CalculateLights", false)) { SkyLightBlockAccess blockAccess = new SkyLightBlockAccess(this, chunk); new SkyLightCalculations().RecalcSkyLight(chunk, blockAccess); //TODO: Block lights. } return(chunk); } } catch (Exception e) { Log.Error($"Loading chunk {coordinates}", e); var chunkColumn = generator?.GenerateChunkColumn(coordinates); if (chunkColumn != null) { //chunkColumn.NeedSave = true; } return(chunkColumn); } }
public ChunkColumn GetChunk(ChunkCoordinates coordinates, IWorldGenerator generator) { var index = BitConverter.GetBytes(coordinates.X).Concat(BitConverter.GetBytes(coordinates.Z)).ToArray(); var versionKey = index.Concat(new byte[] { 0x76 }).ToArray(); var version = _db.Get(versionKey); ChunkColumn chunkColumn = null; if (version != null && version.First() == 10) { chunkColumn = new ChunkColumn(); chunkColumn.X = coordinates.X; chunkColumn.Z = coordinates.Z; for (byte y = 0; y < 16; y++) { var chunkDataKey = index.Concat(new byte[] { 0x2f, y }).ToArray(); var sectionBytes = _db.Get(chunkDataKey); if (sectionBytes == null) { for (; y < 16; y++) { chunkColumn[y]?.PutPool(); chunkColumn[y] = null; } break; } ParseSection((SubChunk)chunkColumn[y], sectionBytes); } // Biomes var flatDataBytes = _db.Get(index.Concat(new byte[] { 0x2D }).ToArray()); if (flatDataBytes != null) { chunkColumn.biomeId = flatDataBytes.AsSpan().Slice(512, 256).ToArray(); } // Block entities var blockEntityBytes = _db.Get(index.Concat(new byte[] { 0x31 }).ToArray()); if (blockEntityBytes != null) { var data = blockEntityBytes.AsSpan(); var file = new NbtFile { BigEndian = false, UseVarInt = false }; int position = 0; do { position += (int)file.LoadFromStream(new MemoryStream(data.Slice(position).ToArray()), NbtCompression.None); var blockEntityTag = file.RootTag; int x = blockEntityTag["x"].IntValue; int y = blockEntityTag["y"].IntValue; int z = blockEntityTag["z"].IntValue; chunkColumn.SetBlockEntity(new BlockCoordinates(x, y, z), (NbtCompound)file.RootTag); } while (position < data.Length); } } if (chunkColumn == null) { if (version != null) { Log.Error($"Expected other version, but got version={version.First()}"); } chunkColumn = generator?.GenerateChunkColumn(coordinates); if (chunkColumn != null) { if (Dimension == Dimension.Overworld && Config.GetProperty("CalculateLights", false)) { var blockAccess = new SkyLightBlockAccess(this, chunkColumn); new SkyLightCalculations().RecalcSkyLight(chunkColumn, blockAccess); } chunkColumn.IsDirty = false; chunkColumn.NeedSave = false; } } chunkColumn?.RecalcHeight(); if (Dimension == Dimension.Overworld && Config.GetProperty("CalculateLights", false)) { SkyLightBlockAccess blockAccess = new SkyLightBlockAccess(this, chunkColumn); new SkyLightCalculations().RecalcSkyLight(chunkColumn, blockAccess); //TODO: Block lights. } return(chunkColumn); }
public void Calculate(Level level, BlockCoordinates coordinates) { int currentLight = level.GetSkyLight(coordinates); var chunk = level.GetChunk(coordinates); var height = chunk.GetRecalatedHeight(coordinates.X & 0x0f, coordinates.Z & 0x0f); Queue <BlockCoordinates> sourceQueue = new Queue <BlockCoordinates>(); sourceQueue.Enqueue(coordinates); if (currentLight != 0) { Queue <BlockCoordinates> resetQueue = new Queue <BlockCoordinates>(); HashSet <BlockCoordinates> visits = new HashSet <BlockCoordinates>(); // Reset all lights that potentially derive from this resetQueue.Enqueue(coordinates); Queue <BlockCoordinates> deleteQueue = new Queue <BlockCoordinates>(); while (resetQueue.Count > 0) { var coord = resetQueue.Dequeue(); if (visits.Contains(coord)) { continue; } visits.Add(coord); if (coord.DistanceTo(coordinates) > 16) { continue; } ResetLight(level, resetQueue, sourceQueue, coord); if (!sourceQueue.Contains(coord)) { deleteQueue.Enqueue(coord); } } level.SetSkyLight(coordinates, 0); foreach (var delete in deleteQueue) { level.SetSkyLight(delete, 0); } } else { sourceQueue.Enqueue(coordinates); sourceQueue.Enqueue(coordinates.BlockUp()); sourceQueue.Enqueue(coordinates.BlockDown()); sourceQueue.Enqueue(coordinates.BlockWest()); sourceQueue.Enqueue(coordinates.BlockEast()); sourceQueue.Enqueue(coordinates.BlockNorth()); sourceQueue.Enqueue(coordinates.BlockSouth()); } chunk.SetHeight(coordinates.X & 0x0f, coordinates.Z & 0x0f, (short)height); // Recalc Queue <BlockCoordinates> lightBfQueue = new Queue <BlockCoordinates>(sourceQueue); HashSet <BlockCoordinates> lightBfSet = new HashSet <BlockCoordinates>(sourceQueue); SkyLightBlockAccess blockAccess = new SkyLightBlockAccess(level.WorldProvider); Calculate(blockAccess, lightBfQueue, lightBfSet); }
public ChunkColumn GetChunk(ChunkCoordinates coordinates, IWorldGenerator generator) { var sw = Stopwatch.StartNew(); byte[] index = Combine(BitConverter.GetBytes(coordinates.X), BitConverter.GetBytes(coordinates.Z)); byte[] versionKey = Combine(index, 0x76); byte[] version = _db.Get(versionKey); ChunkColumn chunkColumn = null; if (version != null && version.First() >= 10) { chunkColumn = new ChunkColumn { X = coordinates.X, Z = coordinates.Z }; var chunkDataKey = Combine(index, new byte[] { 0x2f, 0 }); for (byte y = 0; y < 16; y++) { chunkDataKey[9] = y; byte[] sectionBytes = _db.Get(chunkDataKey); if (sectionBytes == null) { chunkColumn[y]?.PutPool(); chunkColumn[y] = null; continue; } ParseSection(chunkColumn[y], sectionBytes); } // Biomes var flatDataBytes = _db.Get(Combine(index, 0x2D)); if (flatDataBytes != null) { chunkColumn.biomeId = flatDataBytes.AsSpan().Slice(512, 256).ToArray(); } // Block entities byte[] blockEntityBytes = _db.Get(Combine(index, 0x31)); if (blockEntityBytes != null) { var data = blockEntityBytes.AsMemory(); var file = new NbtFile { BigEndian = false, UseVarInt = false }; int position = 0; do { position += (int)file.LoadFromStream(new MemoryStreamReader(data.Slice(position)), NbtCompression.None); NbtTag blockEntityTag = file.RootTag; int x = blockEntityTag["x"].IntValue; int y = blockEntityTag["y"].IntValue; int z = blockEntityTag["z"].IntValue; chunkColumn.SetBlockEntity(new BlockCoordinates(x, y, z), (NbtCompound)blockEntityTag); } while (position < data.Length); } } if (chunkColumn == null) { if (version != null) { Log.Error($"Expected other version, but got version={version.First()}"); } chunkColumn = generator?.GenerateChunkColumn(coordinates); } if (chunkColumn != null) { if (Dimension == Dimension.Overworld && Config.GetProperty("CalculateLights", false)) { chunkColumn.RecalcHeight(); var blockAccess = new SkyLightBlockAccess(this, chunkColumn); new SkyLightCalculations().RecalcSkyLight(chunkColumn, blockAccess); //TODO: Block lights. } chunkColumn.IsDirty = false; chunkColumn.NeedSave = false; } //Log.Debug($"Read chunk {coordinates.X}, {coordinates.Z} in {sw.ElapsedMilliseconds}ms"); return(chunkColumn); }