diff --git a/TrueCraft.API/World/IChunk.cs b/TrueCraft.API/World/IChunk.cs index 886536c..bfeb4f7 100644 --- a/TrueCraft.API/World/IChunk.cs +++ b/TrueCraft.API/World/IChunk.cs @@ -8,6 +8,7 @@ namespace TrueCraft.API.World { int X { get; } int Z { get; } + int MaxHeight { get; } Coordinates2D Coordinates { get; set; } bool IsModified { get; set; } bool LightPopulated { get; set; } diff --git a/TrueCraft.API/World/IWorld.cs b/TrueCraft.API/World/IWorld.cs index 95ff086..5b4c643 100644 --- a/TrueCraft.API/World/IWorld.cs +++ b/TrueCraft.API/World/IWorld.cs @@ -29,7 +29,6 @@ namespace TrueCraft.API.World byte GetMetadata(Coordinates3D coordinates); byte GetBlockLight(Coordinates3D coordinates); byte GetSkyLight(Coordinates3D coordinates); - Coordinates3D AdjustCoordinates(Coordinates3D coordinates); Coordinates3D FindBlockPosition(Coordinates3D coordinates, out IChunk chunk, bool generate = true); NbtCompound GetTileEntity(Coordinates3D coordinates); BlockDescriptor GetBlockData(Coordinates3D coordinates); diff --git a/TrueCraft.Core.Test/Lighting/WorldLighterTest.cs b/TrueCraft.Core.Test/Lighting/WorldLighterTest.cs index c0bdbd8..7b23ddb 100644 --- a/TrueCraft.Core.Test/Lighting/WorldLighterTest.cs +++ b/TrueCraft.Core.Test/Lighting/WorldLighterTest.cs @@ -26,17 +26,13 @@ namespace TrueCraft.Core.Test.Lighting world.BlockRepository = repository; var lighter = new WorldLighting(world, repository); world.GetBlockID(Coordinates3D.Zero); // Generate a chunk - lighter.EnqueueOperation(new BoundingBox( - new Vector3(0, 0, 0), - new Vector3(16, 128, 16)), true, true); - while (lighter.TryLightNext()) - { - } + lighter.InitialLighting(world.GetChunk(Coordinates2D.Zero)); for (int y = 5; y >= 0; y--) { + Console.Write("Y: {0} ", y); Console.Write(world.GetBlockID(new Coordinates3D(0, y, 0))); - Console.Write(" "); + Console.Write(" -> "); Console.WriteLine(world.GetSkyLight(new Coordinates3D(0, y, 0))); } @@ -50,9 +46,9 @@ namespace TrueCraft.Core.Test.Lighting var coords = new Coordinates3D(x, y, z); var sky = world.GetSkyLight(coords); if (y < 4) - Assert.AreEqual(0, sky); + Assert.AreEqual(0, sky, coords.ToString()); else - Assert.AreEqual(15, sky); + Assert.AreEqual(15, sky, coords.ToString()); } } } @@ -70,12 +66,7 @@ namespace TrueCraft.Core.Test.Lighting world.BlockRepository = repository; var lighter = new WorldLighting(world, repository); world.GetBlockID(Coordinates3D.Zero); // Generate a chunk - lighter.EnqueueOperation(new BoundingBox( - new Vector3(0, 0, 0), - new Vector3(16, 128, 16)), true, true); - while (lighter.TryLightNext()) // Initial lighting - { - } + lighter.InitialLighting(world.GetChunk(Coordinates2D.Zero)); world.SetBlockID(new Coordinates3D(5, 3, 5), 0); // Create area that looks like so: world.SetBlockID(new Coordinates3D(5, 2, 5), 0); // x x Light goes like so: | @@ -111,12 +102,7 @@ namespace TrueCraft.Core.Test.Lighting world.BlockRepository = repository; var lighter = new WorldLighting(world, repository); world.GetBlockID(Coordinates3D.Zero); // Generate a chunk - lighter.EnqueueOperation(new BoundingBox( - new Vector3(0, 0, 0), - new Vector3(16, 128, 16)), true, true); - while (lighter.TryLightNext()) // Initial lighting - { - } + lighter.InitialLighting(world.GetChunk(Coordinates2D.Zero)); world.SetBlockID(new Coordinates3D(5, 3, 5), 0); // Create area that looks like so: world.SetBlockID(new Coordinates3D(5, 2, 5), 0); // x x Light goes like so: | @@ -166,12 +152,7 @@ namespace TrueCraft.Core.Test.Lighting world.BlockRepository = repository; var lighter = new WorldLighting(world, repository); world.GetBlockID(Coordinates3D.Zero); // Generate a chunk - lighter.EnqueueOperation(new BoundingBox( - new Vector3(0, 0, 0), - new Vector3(16, 128, 16)), true, true); - while (lighter.TryLightNext()) // Initial lighting - { - } + lighter.InitialLighting(world.GetChunk(Coordinates2D.Zero)); // Test this layout: // xxx x y=3 @@ -273,12 +254,7 @@ namespace TrueCraft.Core.Test.Lighting } world.GetChunk(Coordinates2D.Zero).UpdateHeightMap(); - lighter.EnqueueOperation(new BoundingBox( - new Vector3(0, 0, 0), - new Vector3(16, 128, 16)), true, true); - while (lighter.TryLightNext()) // Initial lighting - { - } + lighter.InitialLighting(world.GetChunk(Coordinates2D.Zero)); // Test this layout: // xox o == leaves diff --git a/TrueCraft.Core/Lighting/WorldLighting.cs b/TrueCraft.Core/Lighting/WorldLighting.cs index 8772927..627c461 100644 --- a/TrueCraft.Core/Lighting/WorldLighting.cs +++ b/TrueCraft.Core/Lighting/WorldLighting.cs @@ -4,12 +4,13 @@ using TrueCraft.Core.World; using TrueCraft.API.Logic; using TrueCraft.API; using System.Collections.Generic; - -// https://github.com/SirCmpwn/TrueCraft/wiki/Lighting using System.Diagnostics; namespace TrueCraft.Core.Lighting { + // https://github.com/SirCmpwn/TrueCraft/wiki/Lighting + + // Note: Speed-critical code public class WorldLighting { private static readonly Coordinates3D[] Neighbors = @@ -57,11 +58,11 @@ namespace TrueCraft.Core.Lighting { Coordinates3D coords; var map = new byte[Chunk.Width, Chunk.Depth]; - for (int x = 0; x < Chunk.Width; x++) + for (byte x = 0; x < Chunk.Width; x++) { - for (int z = 0; z < Chunk.Depth; z++) + for (byte z = 0; z < Chunk.Depth; z++) { - for (byte y = (byte)(chunk.GetHeight((byte)x, (byte)z) + 2); y > 0; y--) + for (byte y = (byte)(chunk.GetHeight(x, z) + 2); y > 0; y--) { if (y >= Chunk.Height) continue; @@ -88,9 +89,9 @@ namespace TrueCraft.Core.Lighting if (!HeightMaps.ContainsKey(chunk.Coordinates)) return; var map = HeightMaps[chunk.Coordinates]; - int x = adjusted.X; int z = adjusted.Z; + byte x = (byte)adjusted.X; byte z = (byte)adjusted.Z; Coordinates3D _; - for (byte y = (byte)(chunk.GetHeight((byte)x, (byte)z) + 2); y > 0; y--) + for (byte y = (byte)(chunk.GetHeight(x, z) + 2); y > 0; y--) { if (y >= Chunk.Height) continue; @@ -120,33 +121,6 @@ namespace TrueCraft.Core.Lighting } } - private IChunk CurrentChunk; - - private Coordinates3D AdjustCoordinates(Coordinates3D coords, out IChunk chunk) - { - int chunkX = coords.X / Chunk.Width; - int chunkZ = coords.Z / Chunk.Depth; - - if (coords.X < 0) - chunkX--; - if (coords.Z < 0) - chunkZ--; - - // Use a cached chunk if possible - if (CurrentChunk != null && chunkX == CurrentChunk.Coordinates.X && chunkZ == CurrentChunk.Coordinates.Z) - chunk = CurrentChunk; - else - { - CurrentChunk = World.GetChunk(new Coordinates2D(chunkX, chunkZ), generate: false); - chunk = CurrentChunk; - } - - return new Coordinates3D( - (coords.X - chunkX * Chunk.Width) % Chunk.Width, - coords.Y, - (coords.Z - chunkZ * Chunk.Depth) % Chunk.Depth); - } - /// /// Propegates a lighting change to an adjacent voxel (if neccesary) /// @@ -156,7 +130,7 @@ namespace TrueCraft.Core.Lighting if (!World.IsValidPosition(coords)) return; IChunk chunk; - var adjustedCoords = AdjustCoordinates(coords, out chunk); + var adjustedCoords = World.FindBlockPosition(coords, out chunk, generate: false); if (chunk == null || !chunk.TerrainPopulated) return; byte current = op.SkyLight ? World.GetSkyLight(coords) : World.GetBlockLight(coords); @@ -167,9 +141,7 @@ namespace TrueCraft.Core.Lighting { byte emissiveness = provider.Luminance; if (chunk.GetHeight((byte)adjustedCoords.X, (byte)adjustedCoords.Z) <= y) - { emissiveness = 15; - } if (emissiveness >= current) return; } @@ -184,7 +156,7 @@ namespace TrueCraft.Core.Lighting var coords = new Coordinates3D(x, y, z); IChunk chunk; - var adjustedCoords = AdjustCoordinates(coords, out chunk); + var adjustedCoords = World.FindBlockPosition(coords, out chunk, generate: false); if (chunk == null || !chunk.TerrainPopulated) // Move on if this chunk is empty return; @@ -246,9 +218,9 @@ namespace TrueCraft.Core.Lighting { // Apply changes if (op.SkyLight) - World.SetSkyLight(coords, final); + chunk.SetSkyLight(adjustedCoords, final); else - World.SetBlockLight(coords, final); + chunk.SetBlockLight(adjustedCoords, final); byte propegated = (byte)Math.Max(final - 1, 0); @@ -296,5 +268,29 @@ namespace TrueCraft.Core.Lighting PendingOperations.Add(new LightingOperation { SkyLight = skyLight, Box = box, Initial = initial }); } } + + private void SetUpperVoxels(IChunk chunk) + { + for (int x = 0; x < Chunk.Width; x++) + for (int z = 0; z < Chunk.Depth; z++) + for (int y = chunk.MaxHeight + 1; y < Chunk.Height; y++) + chunk.SetSkyLight(new Coordinates3D(x, y, z), 15); + } + + /// + /// Queues the initial lighting pass for a newly generated chunk. + /// + public void InitialLighting(IChunk chunk, bool flush = true) + { + // Set voxels above max height to 0xFF + SetUpperVoxels(chunk); + var coords = chunk.Coordinates * new Coordinates2D(Chunk.Width, Chunk.Depth); + EnqueueOperation(new BoundingBox(new Vector3(coords.X, 0, coords.Z), + new Vector3(coords.X + Chunk.Width, chunk.MaxHeight + 2, coords.Z + Chunk.Depth)), + true, true); + while (flush && TryLightNext()) + { + } + } } } \ No newline at end of file diff --git a/TrueCraft.Core/World/Chunk.cs b/TrueCraft.Core/World/Chunk.cs index b8838df..18e6c7f 100644 --- a/TrueCraft.Core/World/Chunk.cs +++ b/TrueCraft.Core/World/Chunk.cs @@ -34,6 +34,7 @@ namespace TrueCraft.Core.World public NibbleArray SkyLight { get; set; } public byte[] Biomes { get; set; } public int[] HeightMap { get; set; } + public int MaxHeight { get; private set; } [TagName("xPos")] public int X { get; set; } [TagName("zPos")] @@ -81,6 +82,7 @@ namespace TrueCraft.Core.World TileEntities = new Dictionary(); TerrainPopulated = false; LightPopulated = false; + MaxHeight = 0; } public Chunk(Coordinates2D coordinates) : this() @@ -139,7 +141,11 @@ namespace TrueCraft.Core.World { coordinates.Y--; if (GetBlockID(coordinates) != 0) + { SetHeight((byte)coordinates.X, (byte)coordinates.Z, coordinates.Y); + if (coordinates.Y > MaxHeight) + MaxHeight = coordinates.Y; + } } } } @@ -232,6 +238,8 @@ namespace TrueCraft.Core.World if (Blocks[index] != 0) { SetHeight(x, z, y); + if (y > MaxHeight) + MaxHeight = y; break; } } diff --git a/TrueCraft.Core/World/World.cs b/TrueCraft.Core/World/World.cs index 920e45b..1ccc207 100644 --- a/TrueCraft.Core/World/World.cs +++ b/TrueCraft.Core/World/World.cs @@ -315,24 +315,7 @@ namespace TrueCraft.Core.World Save(); } - public Coordinates3D AdjustCoordinates(Coordinates3D coordinates) - { - if (coordinates.Y < 0 || coordinates.Y >= Chunk.Height) - throw new ArgumentOutOfRangeException("coordinates", "Coordinates are out of range"); - - int chunkX = coordinates.X / Chunk.Width; - int chunkZ = coordinates.Z / Chunk.Depth; - - if (coordinates.X < 0) - chunkX--; - if (coordinates.Z < 0) - chunkZ--; - - return new Coordinates3D( - (coordinates.X - chunkX * Chunk.Width) % Chunk.Width, - coordinates.Y, - (coordinates.Z - chunkZ * Chunk.Depth) % Chunk.Depth); - } + private IChunk CachedChunk; public Coordinates3D FindBlockPosition(Coordinates3D coordinates, out IChunk chunk, bool generate = true) { @@ -343,15 +326,23 @@ namespace TrueCraft.Core.World int chunkZ = coordinates.Z / Chunk.Depth; if (coordinates.X < 0) - chunkX--; + chunkX = (coordinates.X + 1) / Chunk.Width - 1; if (coordinates.Z < 0) - chunkZ--; + chunkZ = (coordinates.Z + 1) / Chunk.Depth - 1; + + if (CachedChunk != null && chunkX == CachedChunk.Coordinates.X && chunkZ == CachedChunk.Coordinates.Z) + chunk = CachedChunk; + else + { + CachedChunk = GetChunk(new Coordinates2D(chunkX, chunkZ), generate); + chunk = CachedChunk; + } chunk = GetChunk(new Coordinates2D(chunkX, chunkZ), generate); return new Coordinates3D( - (coordinates.X - chunkX * Chunk.Width) % Chunk.Width, + (coordinates.X % Chunk.Width + Chunk.Width) % Chunk.Width, coordinates.Y, - (coordinates.Z - chunkZ * Chunk.Depth) % Chunk.Depth); + (coordinates.Z % Chunk.Depth + Chunk.Depth) % Chunk.Depth); } public bool IsValidPosition(Coordinates3D position) diff --git a/TrueCraft/Commands/DebugCommands.cs b/TrueCraft/Commands/DebugCommands.cs index 97df4ad..748f151 100644 --- a/TrueCraft/Commands/DebugCommands.cs +++ b/TrueCraft/Commands/DebugCommands.cs @@ -45,6 +45,38 @@ namespace TrueCraft.Commands } } + public class SkyLightCommand : Command + { + public override string Name + { + get { return "sl"; } + } + + public override string Description + { + get { return "Shows sky light at your current position."; } + } + + public override string[] Aliases + { + get { return new string[0]; } + } + + public override void Handle(IRemoteClient client, string alias, string[] arguments) + { + int mod = 0; + if (arguments.Length == 1) + int.TryParse(arguments[0], out mod); + client.SendMessage(client.World.GetSkyLight( + (Coordinates3D)(client.Entity.Position + new Vector3(0, -mod, 0))).ToString()); + } + + public override void Help(IRemoteClient client, string alias, string[] arguments) + { + client.SendMessage("/sl"); + } + } + public class SpawnCommand : Command { public override string Name diff --git a/TrueCraft/MultiplayerServer.cs b/TrueCraft/MultiplayerServer.cs index 67617f4..6ecce65 100644 --- a/TrueCraft/MultiplayerServer.cs +++ b/TrueCraft/MultiplayerServer.cs @@ -191,16 +191,7 @@ namespace TrueCraft if (Program.ServerConfiguration.EnableLighting) { var lighter = new WorldLighting(sender as IWorld, BlockRepository); - var coords = e.Coordinates * new Coordinates2D(Chunk.Width, Chunk.Depth); - var watch = new Stopwatch(); - watch.Start(); - lighter.EnqueueOperation(new BoundingBox(new Vector3(coords.X, 0, coords.Z), - new Vector3(coords.X + Chunk.Width, Chunk.Height, coords.Z + Chunk.Depth)), true, true); - while (lighter.TryLightNext()) // Initial lighting - { - } - watch.Stop(); - Console.WriteLine("Initial chunk lighting took {0}ms", watch.ElapsedMilliseconds); + lighter.InitialLighting(e.Chunk); } else { diff --git a/TrueCraft/ServerConfiguration.cs b/TrueCraft/ServerConfiguration.cs index 6f7f516..b09cf29 100644 --- a/TrueCraft/ServerConfiguration.cs +++ b/TrueCraft/ServerConfiguration.cs @@ -30,31 +30,31 @@ namespace TrueCraft Singleplayer = false; Query = true; QueryPort = 25566; - EnableLighting = false; + EnableLighting = true; } [YamlMember(Alias = "motd")] public string MOTD { get; set; } - [YamlMember(Alias = "serverPort")] + [YamlMember(Alias = "bind-port")] public int ServerPort {get; set; } - [YamlMember(Alias = "serverAddress")] + [YamlMember(Alias = "bind-endpoint")] public string ServerAddress { get; set; } [YamlMember(Alias = "debug")] public DebugConfiguration Debug { get; set; } - [YamlMember(Alias = "worldSaveInterval")] + [YamlMember(Alias = "save-interval")] public int WorldSaveInterval { get; set; } [YamlIgnore] public bool Singleplayer { get; set; } - [YamlMember(Alias = "query")] + [YamlMember(Alias = "query-enabled")] public bool Query { get; set; } - [YamlMember(Alias = "queryPort")] + [YamlMember(Alias = "query-port")] public int QueryPort { get; set; } [YamlMember(Alias = "enable-lighting")]