Finish lighting optimizations (for now)

We can now consistently light a chunk with sub-10ms lighting steps.
This commit is contained in:
Drew DeVault 2015-07-04 15:00:54 -06:00
parent d00c4ad9f5
commit 87b621e166
9 changed files with 106 additions and 112 deletions

View File

@ -8,6 +8,7 @@ namespace TrueCraft.API.World
{ {
int X { get; } int X { get; }
int Z { get; } int Z { get; }
int MaxHeight { get; }
Coordinates2D Coordinates { get; set; } Coordinates2D Coordinates { get; set; }
bool IsModified { get; set; } bool IsModified { get; set; }
bool LightPopulated { get; set; } bool LightPopulated { get; set; }

View File

@ -29,7 +29,6 @@ namespace TrueCraft.API.World
byte GetMetadata(Coordinates3D coordinates); byte GetMetadata(Coordinates3D coordinates);
byte GetBlockLight(Coordinates3D coordinates); byte GetBlockLight(Coordinates3D coordinates);
byte GetSkyLight(Coordinates3D coordinates); byte GetSkyLight(Coordinates3D coordinates);
Coordinates3D AdjustCoordinates(Coordinates3D coordinates);
Coordinates3D FindBlockPosition(Coordinates3D coordinates, out IChunk chunk, bool generate = true); Coordinates3D FindBlockPosition(Coordinates3D coordinates, out IChunk chunk, bool generate = true);
NbtCompound GetTileEntity(Coordinates3D coordinates); NbtCompound GetTileEntity(Coordinates3D coordinates);
BlockDescriptor GetBlockData(Coordinates3D coordinates); BlockDescriptor GetBlockData(Coordinates3D coordinates);

View File

@ -26,17 +26,13 @@ namespace TrueCraft.Core.Test.Lighting
world.BlockRepository = repository; world.BlockRepository = repository;
var lighter = new WorldLighting(world, repository); var lighter = new WorldLighting(world, repository);
world.GetBlockID(Coordinates3D.Zero); // Generate a chunk world.GetBlockID(Coordinates3D.Zero); // Generate a chunk
lighter.EnqueueOperation(new BoundingBox( lighter.InitialLighting(world.GetChunk(Coordinates2D.Zero));
new Vector3(0, 0, 0),
new Vector3(16, 128, 16)), true, true);
while (lighter.TryLightNext())
{
}
for (int y = 5; y >= 0; y--) for (int y = 5; y >= 0; y--)
{ {
Console.Write("Y: {0} ", y);
Console.Write(world.GetBlockID(new Coordinates3D(0, y, 0))); Console.Write(world.GetBlockID(new Coordinates3D(0, y, 0)));
Console.Write(" "); Console.Write(" -> ");
Console.WriteLine(world.GetSkyLight(new Coordinates3D(0, y, 0))); 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 coords = new Coordinates3D(x, y, z);
var sky = world.GetSkyLight(coords); var sky = world.GetSkyLight(coords);
if (y < 4) if (y < 4)
Assert.AreEqual(0, sky); Assert.AreEqual(0, sky, coords.ToString());
else else
Assert.AreEqual(15, sky); Assert.AreEqual(15, sky, coords.ToString());
} }
} }
} }
@ -70,12 +66,7 @@ namespace TrueCraft.Core.Test.Lighting
world.BlockRepository = repository; world.BlockRepository = repository;
var lighter = new WorldLighting(world, repository); var lighter = new WorldLighting(world, repository);
world.GetBlockID(Coordinates3D.Zero); // Generate a chunk world.GetBlockID(Coordinates3D.Zero); // Generate a chunk
lighter.EnqueueOperation(new BoundingBox( lighter.InitialLighting(world.GetChunk(Coordinates2D.Zero));
new Vector3(0, 0, 0),
new Vector3(16, 128, 16)), true, true);
while (lighter.TryLightNext()) // Initial lighting
{
}
world.SetBlockID(new Coordinates3D(5, 3, 5), 0); // Create area that looks like so: 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: | 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; world.BlockRepository = repository;
var lighter = new WorldLighting(world, repository); var lighter = new WorldLighting(world, repository);
world.GetBlockID(Coordinates3D.Zero); // Generate a chunk world.GetBlockID(Coordinates3D.Zero); // Generate a chunk
lighter.EnqueueOperation(new BoundingBox( lighter.InitialLighting(world.GetChunk(Coordinates2D.Zero));
new Vector3(0, 0, 0),
new Vector3(16, 128, 16)), true, true);
while (lighter.TryLightNext()) // Initial lighting
{
}
world.SetBlockID(new Coordinates3D(5, 3, 5), 0); // Create area that looks like so: 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: | 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; world.BlockRepository = repository;
var lighter = new WorldLighting(world, repository); var lighter = new WorldLighting(world, repository);
world.GetBlockID(Coordinates3D.Zero); // Generate a chunk world.GetBlockID(Coordinates3D.Zero); // Generate a chunk
lighter.EnqueueOperation(new BoundingBox( lighter.InitialLighting(world.GetChunk(Coordinates2D.Zero));
new Vector3(0, 0, 0),
new Vector3(16, 128, 16)), true, true);
while (lighter.TryLightNext()) // Initial lighting
{
}
// Test this layout: // Test this layout:
// xxx x y=3 // xxx x y=3
@ -273,12 +254,7 @@ namespace TrueCraft.Core.Test.Lighting
} }
world.GetChunk(Coordinates2D.Zero).UpdateHeightMap(); world.GetChunk(Coordinates2D.Zero).UpdateHeightMap();
lighter.EnqueueOperation(new BoundingBox( lighter.InitialLighting(world.GetChunk(Coordinates2D.Zero));
new Vector3(0, 0, 0),
new Vector3(16, 128, 16)), true, true);
while (lighter.TryLightNext()) // Initial lighting
{
}
// Test this layout: // Test this layout:
// xox o == leaves // xox o == leaves

View File

@ -4,12 +4,13 @@ using TrueCraft.Core.World;
using TrueCraft.API.Logic; using TrueCraft.API.Logic;
using TrueCraft.API; using TrueCraft.API;
using System.Collections.Generic; using System.Collections.Generic;
// https://github.com/SirCmpwn/TrueCraft/wiki/Lighting
using System.Diagnostics; using System.Diagnostics;
namespace TrueCraft.Core.Lighting namespace TrueCraft.Core.Lighting
{ {
// https://github.com/SirCmpwn/TrueCraft/wiki/Lighting
// Note: Speed-critical code
public class WorldLighting public class WorldLighting
{ {
private static readonly Coordinates3D[] Neighbors = private static readonly Coordinates3D[] Neighbors =
@ -57,11 +58,11 @@ namespace TrueCraft.Core.Lighting
{ {
Coordinates3D coords; Coordinates3D coords;
var map = new byte[Chunk.Width, Chunk.Depth]; 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) if (y >= Chunk.Height)
continue; continue;
@ -88,9 +89,9 @@ namespace TrueCraft.Core.Lighting
if (!HeightMaps.ContainsKey(chunk.Coordinates)) if (!HeightMaps.ContainsKey(chunk.Coordinates))
return; return;
var map = HeightMaps[chunk.Coordinates]; var map = HeightMaps[chunk.Coordinates];
int x = adjusted.X; int z = adjusted.Z; byte x = (byte)adjusted.X; byte z = (byte)adjusted.Z;
Coordinates3D _; 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) if (y >= Chunk.Height)
continue; 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);
}
/// <summary> /// <summary>
/// Propegates a lighting change to an adjacent voxel (if neccesary) /// Propegates a lighting change to an adjacent voxel (if neccesary)
/// </summary> /// </summary>
@ -156,7 +130,7 @@ namespace TrueCraft.Core.Lighting
if (!World.IsValidPosition(coords)) if (!World.IsValidPosition(coords))
return; return;
IChunk chunk; IChunk chunk;
var adjustedCoords = AdjustCoordinates(coords, out chunk); var adjustedCoords = World.FindBlockPosition(coords, out chunk, generate: false);
if (chunk == null || !chunk.TerrainPopulated) if (chunk == null || !chunk.TerrainPopulated)
return; return;
byte current = op.SkyLight ? World.GetSkyLight(coords) : World.GetBlockLight(coords); byte current = op.SkyLight ? World.GetSkyLight(coords) : World.GetBlockLight(coords);
@ -167,9 +141,7 @@ namespace TrueCraft.Core.Lighting
{ {
byte emissiveness = provider.Luminance; byte emissiveness = provider.Luminance;
if (chunk.GetHeight((byte)adjustedCoords.X, (byte)adjustedCoords.Z) <= y) if (chunk.GetHeight((byte)adjustedCoords.X, (byte)adjustedCoords.Z) <= y)
{
emissiveness = 15; emissiveness = 15;
}
if (emissiveness >= current) if (emissiveness >= current)
return; return;
} }
@ -184,7 +156,7 @@ namespace TrueCraft.Core.Lighting
var coords = new Coordinates3D(x, y, z); var coords = new Coordinates3D(x, y, z);
IChunk chunk; 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 if (chunk == null || !chunk.TerrainPopulated) // Move on if this chunk is empty
return; return;
@ -246,9 +218,9 @@ namespace TrueCraft.Core.Lighting
{ {
// Apply changes // Apply changes
if (op.SkyLight) if (op.SkyLight)
World.SetSkyLight(coords, final); chunk.SetSkyLight(adjustedCoords, final);
else else
World.SetBlockLight(coords, final); chunk.SetBlockLight(adjustedCoords, final);
byte propegated = (byte)Math.Max(final - 1, 0); 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 }); 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);
}
/// <summary>
/// Queues the initial lighting pass for a newly generated chunk.
/// </summary>
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())
{
}
}
} }
} }

View File

@ -34,6 +34,7 @@ namespace TrueCraft.Core.World
public NibbleArray SkyLight { get; set; } public NibbleArray SkyLight { get; set; }
public byte[] Biomes { get; set; } public byte[] Biomes { get; set; }
public int[] HeightMap { get; set; } public int[] HeightMap { get; set; }
public int MaxHeight { get; private set; }
[TagName("xPos")] [TagName("xPos")]
public int X { get; set; } public int X { get; set; }
[TagName("zPos")] [TagName("zPos")]
@ -81,6 +82,7 @@ namespace TrueCraft.Core.World
TileEntities = new Dictionary<Coordinates3D, NbtCompound>(); TileEntities = new Dictionary<Coordinates3D, NbtCompound>();
TerrainPopulated = false; TerrainPopulated = false;
LightPopulated = false; LightPopulated = false;
MaxHeight = 0;
} }
public Chunk(Coordinates2D coordinates) : this() public Chunk(Coordinates2D coordinates) : this()
@ -139,7 +141,11 @@ namespace TrueCraft.Core.World
{ {
coordinates.Y--; coordinates.Y--;
if (GetBlockID(coordinates) != 0) if (GetBlockID(coordinates) != 0)
{
SetHeight((byte)coordinates.X, (byte)coordinates.Z, coordinates.Y); 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) if (Blocks[index] != 0)
{ {
SetHeight(x, z, y); SetHeight(x, z, y);
if (y > MaxHeight)
MaxHeight = y;
break; break;
} }
} }

View File

@ -315,24 +315,7 @@ namespace TrueCraft.Core.World
Save(); Save();
} }
public Coordinates3D AdjustCoordinates(Coordinates3D coordinates) private IChunk CachedChunk;
{
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);
}
public Coordinates3D FindBlockPosition(Coordinates3D coordinates, out IChunk chunk, bool generate = true) 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; int chunkZ = coordinates.Z / Chunk.Depth;
if (coordinates.X < 0) if (coordinates.X < 0)
chunkX--; chunkX = (coordinates.X + 1) / Chunk.Width - 1;
if (coordinates.Z < 0) 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); chunk = GetChunk(new Coordinates2D(chunkX, chunkZ), generate);
return new Coordinates3D( return new Coordinates3D(
(coordinates.X - chunkX * Chunk.Width) % Chunk.Width, (coordinates.X % Chunk.Width + Chunk.Width) % Chunk.Width,
coordinates.Y, coordinates.Y,
(coordinates.Z - chunkZ * Chunk.Depth) % Chunk.Depth); (coordinates.Z % Chunk.Depth + Chunk.Depth) % Chunk.Depth);
} }
public bool IsValidPosition(Coordinates3D position) public bool IsValidPosition(Coordinates3D position)

View File

@ -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 class SpawnCommand : Command
{ {
public override string Name public override string Name

View File

@ -191,16 +191,7 @@ namespace TrueCraft
if (Program.ServerConfiguration.EnableLighting) if (Program.ServerConfiguration.EnableLighting)
{ {
var lighter = new WorldLighting(sender as IWorld, BlockRepository); var lighter = new WorldLighting(sender as IWorld, BlockRepository);
var coords = e.Coordinates * new Coordinates2D(Chunk.Width, Chunk.Depth); lighter.InitialLighting(e.Chunk);
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);
} }
else else
{ {

View File

@ -30,31 +30,31 @@ namespace TrueCraft
Singleplayer = false; Singleplayer = false;
Query = true; Query = true;
QueryPort = 25566; QueryPort = 25566;
EnableLighting = false; EnableLighting = true;
} }
[YamlMember(Alias = "motd")] [YamlMember(Alias = "motd")]
public string MOTD { get; set; } public string MOTD { get; set; }
[YamlMember(Alias = "serverPort")] [YamlMember(Alias = "bind-port")]
public int ServerPort {get; set; } public int ServerPort {get; set; }
[YamlMember(Alias = "serverAddress")] [YamlMember(Alias = "bind-endpoint")]
public string ServerAddress { get; set; } public string ServerAddress { get; set; }
[YamlMember(Alias = "debug")] [YamlMember(Alias = "debug")]
public DebugConfiguration Debug { get; set; } public DebugConfiguration Debug { get; set; }
[YamlMember(Alias = "worldSaveInterval")] [YamlMember(Alias = "save-interval")]
public int WorldSaveInterval { get; set; } public int WorldSaveInterval { get; set; }
[YamlIgnore] [YamlIgnore]
public bool Singleplayer { get; set; } public bool Singleplayer { get; set; }
[YamlMember(Alias = "query")] [YamlMember(Alias = "query-enabled")]
public bool Query { get; set; } public bool Query { get; set; }
[YamlMember(Alias = "queryPort")] [YamlMember(Alias = "query-port")]
public int QueryPort { get; set; } public int QueryPort { get; set; }
[YamlMember(Alias = "enable-lighting")] [YamlMember(Alias = "enable-lighting")]