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 Z { get; }
int MaxHeight { get; }
Coordinates2D Coordinates { get; set; }
bool IsModified { get; set; }
bool LightPopulated { get; set; }

View File

@ -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);

View File

@ -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

View File

@ -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);
}
/// <summary>
/// Propegates a lighting change to an adjacent voxel (if neccesary)
/// </summary>
@ -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);
}
/// <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 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<Coordinates3D, NbtCompound>();
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;
}
}

View File

@ -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)

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 override string Name

View File

@ -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
{

View File

@ -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")]