Finish lighting optimizations (for now)
We can now consistently light a chunk with sub-10ms lighting steps.
This commit is contained in:
parent
d00c4ad9f5
commit
87b621e166
@ -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; }
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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())
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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")]
|
||||
|
Reference in New Issue
Block a user