using System; using System.Collections; using System.Collections.Generic; using Substrate.Utility; namespace Substrate { public class BlockLight { private struct LightRecord { public int x; public int y; public int z; public int str; public LightRecord (int _x, int _y, int _z, int s) { x = _x; y = _y; z = _z; str = s; } } private readonly int _xdim; private readonly int _ydim; private readonly int _zdim; private IBoundedLitBlockCollection _blockset; // Maintains internal state of multi-block relighting algorithms private BitArray _lightbit; private Queue _update; public delegate IBoundedLitBlockCollection NeighborLookupHandler (int relx, int rely, int relz); public event NeighborLookupHandler ResolveNeighbor; public BlockLight (IBoundedLitBlockCollection blockset) { _blockset = blockset; _xdim = _blockset.XDim; _ydim = _blockset.YDim; _zdim = _blockset.ZDim; _lightbit = new BitArray(_blockset.XDim * 3 * _blockset.ZDim * 3 * _blockset.YDim); _update = new Queue(); } public BlockLight (BlockLight bl) { _blockset = bl._blockset; _xdim = bl._xdim; _ydim = bl._ydim; _zdim = bl._zdim; _lightbit = new BitArray(_blockset.XDim * 3 * _blockset.ZDim * 3 * _blockset.YDim); _update = new Queue(); } public void UpdateBlockLight (int lx, int ly, int lz) { BlockKey primary = new BlockKey(lx, ly, lz); _update.Enqueue(primary); BlockInfo info = _blockset.GetInfo(lx, ly, lz); //if (info.Luminance > BlockInfo.MIN_LUMINANCE || info.TransmitsLight) { QueueRelight(new BlockKey(lx - 1, ly, lz)); QueueRelight(new BlockKey(lx + 1, ly, lz)); QueueRelight(new BlockKey(lx, ly - 1, lz)); QueueRelight(new BlockKey(lx, ly + 1, lz)); QueueRelight(new BlockKey(lx, ly, lz - 1)); QueueRelight(new BlockKey(lx, ly, lz + 1)); //} UpdateBlockLight(); } public void UpdateBlockSkyLight (int lx, int ly, int lz) { BlockKey primary = new BlockKey(lx, ly, lz); _update.Enqueue(primary); UpdateBlockSkyLight(); } public void UpdateHeightMap (int lx, int ly, int lz) { BlockInfo info = _blockset.GetInfo(lx, ly, lz); int h = Math.Min(ly + 1, _ydim - 1); int height = _blockset.GetHeight(lx, lz); if (h < height) { return; } if (h == height && !info.ObscuresLight) { for (int i = ly - 1; i >= 0; i--) { BlockInfo info2 = _blockset.GetInfo(lx, i, lz); if (info2.ObscuresLight) { _blockset.SetHeight(lx, lz, Math.Min(i + 1, _ydim - 1)); break; } } UpdateBlockSkyLight(lx, h, lz); } else if (h > height && info.ObscuresLight) { _blockset.SetHeight(lx, lz, h); UpdateBlockSkyLight(lx, h, lz); } } public void RebuildBlockLight () { IBoundedLitBlockCollection[,] chunkMap = LocalBlockLightMap(); // Because the JIT is less intelligent than I hoped int xdim = _xdim; int ydim = _ydim; int zdim = _zdim; for (int x = 0; x < xdim; x++) { for (int z = 0; z < zdim; z++) { for (int y = 0; y < ydim; y++) { BlockInfo info = _blockset.GetInfo(x, y, z); if (info.Luminance > 0) { SpreadBlockLight(chunkMap, x, y, z); } } } } } public void RebuildBlockSkyLight () { IBoundedLitBlockCollection[,] chunkMap = LocalBlockLightMap(); int[,] heightMap = LocalHeightMap(chunkMap); int xdim = _xdim; int ydim = _ydim; int zdim = _zdim; // Optimization - only need to queue at level of highest neighbor's height for (int x = 0; x < xdim; x++) { for (int z = 0; z < zdim; z++) { int xi = x + xdim; int zi = z + zdim; int h = heightMap[xi, zi]; h = Math.Max(h, heightMap[xi, zi - 1]); h = Math.Max(h, heightMap[xi - 1, zi]); h = Math.Max(h, heightMap[xi + 1, zi]); h = Math.Max(h, heightMap[xi, zi + 1]); for (int y = h + 1; y < ydim; y++) { _blockset.SetSkyLight(x, y, z, BlockInfo.MAX_LUMINANCE); } //QueueRelight(new BlockKey(x, h, z)); SpreadSkyLight(chunkMap, heightMap, x, h, z); } } } public void RebuildHeightMap () { int xdim = _xdim; int ydim = _ydim; int zdim = _zdim; for (int x = 0; x < xdim; x++) { for (int z = 0; z < zdim; z++) { for (int y = ydim - 1; y >= 0; y--) { BlockInfo info = _blockset.GetInfo(x, y, z); if (info.ObscuresLight) { _blockset.SetHeight(x, z, Math.Min(y + 1, ydim - 1)); break; } } } } } public void StitchBlockLight () { IBoundedLitBlockCollection[,] map = LocalBlockLightMap(); if (map[1, 0] != null) { StitchBlockLight(map[1, 0], BlockCollectionEdge.EAST); } if (map[0, 1] != null) { StitchBlockLight(map[0, 1], BlockCollectionEdge.NORTH); } if (map[1, 2] != null) { StitchBlockLight(map[1, 2], BlockCollectionEdge.WEST); } if (map[2, 1] != null) { StitchBlockLight(map[2, 1], BlockCollectionEdge.SOUTH); } } // TODO: Revise to cache the specified chunk into local map public void StitchBlockLight (IBoundedLitBlockCollection chunk, BlockCollectionEdge edge) { int xdim = _xdim; int ydim = _ydim; int zdim = _zdim; if (chunk.XDim != xdim || chunk.YDim != ydim || chunk.ZDim != zdim) { throw new InvalidOperationException("BlockLight must have same dimensions to be stitched"); } switch (edge) { case BlockCollectionEdge.EAST: for (int x = 0; x < xdim; x++) { for (int y = 0; y < ydim; y++) { TestBlockLight(chunk, x, y, 0, x, y, zdim - 1); } } break; case BlockCollectionEdge.NORTH: for (int z = 0; z < zdim; z++) { for (int y = 0; y < ydim; y++) { TestBlockLight(chunk, 0, y, z, xdim - 1, y, z); } } break; case BlockCollectionEdge.WEST: for (int x = 0; x < xdim; x++) { for (int y = 0; y < ydim; y++) { TestBlockLight(chunk, x, y, zdim - 1, x, y, 0); } } break; case BlockCollectionEdge.SOUTH: for (int z = 0; z < zdim; z++) { for (int y = 0; y < ydim; y++) { TestBlockLight(chunk, xdim - 1, y, z, 0, y, z); } } break; } UpdateBlockLight(); } public void StitchBlockSkyLight () { IBoundedLitBlockCollection[,] map = LocalBlockLightMap(); if (map[1, 0] != null) { StitchBlockSkyLight(map[1, 0], BlockCollectionEdge.EAST); } if (map[0, 1] != null) { StitchBlockSkyLight(map[0, 1], BlockCollectionEdge.NORTH); } if (map[1, 2] != null) { StitchBlockSkyLight(map[1, 2], BlockCollectionEdge.WEST); } if (map[2, 1] != null) { StitchBlockSkyLight(map[2, 1], BlockCollectionEdge.SOUTH); } } public void StitchBlockSkyLight (IBoundedLitBlockCollection chunk, BlockCollectionEdge edge) { int xdim = _xdim; int ydim = _ydim; int zdim = _zdim; if (chunk.XDim != xdim || chunk.YDim != ydim || chunk.ZDim != zdim) { throw new InvalidOperationException("BlockLight must have same dimensions to be stitched"); } switch (edge) { case BlockCollectionEdge.EAST: for (int x = 0; x < xdim; x++) { for (int y = 0; y < ydim; y++) { TestSkyLight(chunk, x, y, 0, x, y, zdim - 1); } } break; case BlockCollectionEdge.NORTH: for (int z = 0; z < zdim; z++) { for (int y = 0; y < ydim; y++) { TestSkyLight(chunk, 0, y, z, xdim, y, z); } } break; case BlockCollectionEdge.WEST: for (int x = 0; x < xdim; x++) { for (int y = 0; y < ydim; y++) { TestSkyLight(chunk, x, y, zdim - 1, x, y, 0); } } break; case BlockCollectionEdge.SOUTH: for (int z = 0; z < zdim; z++) { for (int y = 0; y < ydim; y++) { TestSkyLight(chunk, xdim - 1, y, z, 0, y, z); } } break; } UpdateBlockSkyLight(); } private void UpdateBlockLight () { IBoundedLitBlockCollection[,] chunkMap = LocalBlockLightMap(); int xdim = _xdim; int ydim = _ydim; int zdim = _zdim; while (_update.Count > 0) { BlockKey k = _update.Dequeue(); int index = LightBitmapIndex(k); _lightbit[index] = false; int xi = k.x + xdim; int zi = k.z + zdim; IBoundedLitBlockCollection cc = chunkMap[xi / xdim, zi / zdim]; if (cc == null) { continue; } int lle = NeighborLight(chunkMap, k.x, k.y, k.z - 1); int lln = NeighborLight(chunkMap, k.x - 1, k.y, k.z); int lls = NeighborLight(chunkMap, k.x, k.y, k.z + 1); int llw = NeighborLight(chunkMap, k.x + 1, k.y, k.z); int lld = NeighborLight(chunkMap, k.x, k.y - 1, k.z); int llu = NeighborLight(chunkMap, k.x, k.y + 1, k.z); int x = xi % xdim; int y = k.y; int z = zi % zdim; int lightval = cc.GetBlockLight(x, y, z); BlockInfo info = cc.GetInfo(x, y, z); int light = Math.Max(info.Luminance, 0); light = Math.Max(light, lle); light = Math.Max(light, lln); light = Math.Max(light, lls); light = Math.Max(light, llw); light = Math.Max(light, lld); light = Math.Max(light, llu); light = Math.Max(light - info.Opacity, 0); if (light != lightval) { //Console.WriteLine("Block Light: ({0},{1},{2}) " + lightval + " -> " + light, k.x, k.y, k.z); cc.SetBlockLight(x, y, z, light); if (info.TransmitsLight) { if (k.y > 0) { QueueRelight(new BlockKey(k.x, k.y - 1, k.z)); } if (k.y < ydim - 1) { QueueRelight(new BlockKey(k.x, k.y + 1, k.z)); } QueueRelight(new BlockKey(k.x - 1, k.y, k.z)); QueueRelight(new BlockKey(k.x + 1, k.y, k.z)); QueueRelight(new BlockKey(k.x, k.y, k.z - 1)); QueueRelight(new BlockKey(k.x, k.y, k.z + 1)); } } } } private void UpdateBlockSkyLight () { IBoundedLitBlockCollection[,] chunkMap = LocalBlockLightMap(); int xdim = _xdim; int ydim = _ydim; int zdim = _zdim; while (_update.Count > 0) { BlockKey k = _update.Dequeue(); int index = LightBitmapIndex(k); _lightbit[index] = false; int xi = k.x + xdim; int zi = k.z + zdim; IBoundedLitBlockCollection cc = chunkMap[xi / xdim, zi / zdim]; if (cc == null) { continue; } int x = xi % xdim; int y = k.y; int z = zi % zdim; int lightval = cc.GetSkyLight(x, y, z); BlockInfo info = cc.GetInfo(x, y, z); int light = BlockInfo.MIN_LUMINANCE; if (cc.GetHeight(x, z) <= y) { light = BlockInfo.MAX_LUMINANCE; } else { int lle = NeighborSkyLight(chunkMap, k.x, k.y, k.z - 1); int lln = NeighborSkyLight(chunkMap, k.x - 1, k.y, k.z); int lls = NeighborSkyLight(chunkMap, k.x, k.y, k.z + 1); int llw = NeighborSkyLight(chunkMap, k.x + 1, k.y, k.z); int lld = NeighborSkyLight(chunkMap, k.x, k.y - 1, k.z); int llu = NeighborSkyLight(chunkMap, k.x, k.y + 1, k.z); light = Math.Max(light, lle); light = Math.Max(light, lln); light = Math.Max(light, lls); light = Math.Max(light, llw); light = Math.Max(light, lld); light = Math.Max(light, llu); } light = Math.Max(light - info.Opacity, 0); if (light != lightval) { //Console.WriteLine("Block SkyLight: ({0},{1},{2}) " + lightval + " -> " + light, k.x, k.y, k.z); cc.SetSkyLight(x, y, z, light); if (info.TransmitsLight) { if (k.y > 0) { QueueRelight(new BlockKey(k.x, k.y - 1, k.z)); } if (k.y < ydim - 1) { QueueRelight(new BlockKey(k.x, k.y + 1, k.z)); } QueueRelight(new BlockKey(k.x - 1, k.y, k.z)); QueueRelight(new BlockKey(k.x + 1, k.y, k.z)); QueueRelight(new BlockKey(k.x, k.y, k.z - 1)); QueueRelight(new BlockKey(k.x, k.y, k.z + 1)); } } } } private void SpreadBlockLight (IBoundedLitBlockCollection[,] chunkMap, int lx, int ly, int lz) { BlockInfo primary = _blockset.GetInfo(lx, ly, lz); int primaryLight = _blockset.GetBlockLight(lx, ly, lz); int priLum = Math.Max(primary.Luminance - primary.Opacity, 0); if (primaryLight < priLum) { _blockset.SetBlockLight(lx, ly, lz, priLum); } if (primaryLight > primary.Luminance - 1 && !primary.TransmitsLight) { return; } int xdim = _xdim; int ydim = _ydim; int zdim = _zdim; Queue spread = new Queue(); if (ly > 0) { spread.Enqueue(new LightRecord(lx, ly - 1, lz, primary.Luminance - 1)); } if (ly < ydim - 1) { spread.Enqueue(new LightRecord(lx, ly + 1, lz, primary.Luminance - 1)); } spread.Enqueue(new LightRecord(lx - 1, ly, lz, primary.Luminance - 1)); spread.Enqueue(new LightRecord(lx + 1, ly, lz, primary.Luminance - 1)); spread.Enqueue(new LightRecord(lx, ly, lz - 1, primary.Luminance - 1)); spread.Enqueue(new LightRecord(lx, ly, lz + 1, primary.Luminance - 1)); while (spread.Count > 0) { LightRecord rec = spread.Dequeue(); int xi = rec.x + xdim; int zi = rec.z + zdim; IBoundedLitBlockCollection cc = chunkMap[xi / xdim, zi / zdim]; if (cc == null) { continue; } int x = xi % xdim; int y = rec.y; int z = zi % zdim; BlockInfo info = cc.GetInfo(x, y, z); int light = cc.GetBlockLight(x, y, z); int dimStr = Math.Max(rec.str - info.Opacity, 0); if (dimStr > light) { cc.SetBlockLight(x, y, z, dimStr); if (info.TransmitsLight) { int strength = (info.Opacity > 0) ? dimStr : dimStr - 1; if (rec.y > 0) { spread.Enqueue(new LightRecord(rec.x, rec.y - 1, rec.z, strength)); } if (rec.y < ydim - 1) { spread.Enqueue(new LightRecord(rec.x, rec.y + 1, rec.z, strength)); } spread.Enqueue(new LightRecord(rec.x - 1, rec.y, rec.z, strength)); spread.Enqueue(new LightRecord(rec.x + 1, rec.y, rec.z, strength)); spread.Enqueue(new LightRecord(rec.x, rec.y, rec.z - 1, strength)); spread.Enqueue(new LightRecord(rec.x, rec.y, rec.z + 1, strength)); } } } } private void SpreadSkyLight (IBoundedLitBlockCollection[,] chunkMap, int[,] heightMap, int lx, int ly, int lz) { BlockInfo primary = _blockset.GetInfo(lx, ly, lz); int primaryLight = _blockset.GetSkyLight(lx, ly, lz); int priLum = Math.Max(BlockInfo.MAX_LUMINANCE - primary.Opacity, 0); if (primaryLight < priLum) { _blockset.SetSkyLight(lx, ly, lz, priLum); } if (primaryLight > BlockInfo.MAX_LUMINANCE - 1 || !primary.TransmitsLight) { return; } Queue spread = new Queue(); int xdim = _xdim; int ydim = _ydim; int zdim = _zdim; int lxi = lx + xdim; int lzi = lz + zdim; int strength = (primary.Opacity > 0) ? priLum : priLum - 1; if (ly > 0) { if (heightMap[lxi, lzi] > ly - 1) { spread.Enqueue(new LightRecord(lx, ly - 1, lz, strength)); } else { spread.Enqueue(new LightRecord(lx, ly - 1, lz, priLum)); } } if (ly < ydim - 1) { if (heightMap[lxi, lzi] > ly + 1) { spread.Enqueue(new LightRecord(lx, ly + 1, lz, strength)); } } if (heightMap[lxi - 1, lzi] > ly) { spread.Enqueue(new LightRecord(lx - 1, ly, lz, strength)); } if (heightMap[lxi + 1, lzi] > ly) { spread.Enqueue(new LightRecord(lx + 1, ly, lz, strength)); } if (heightMap[lxi, lzi - 1] > ly) { spread.Enqueue(new LightRecord(lx, ly, lz - 1, strength)); } if (heightMap[lxi, lzi + 1] > ly) { spread.Enqueue(new LightRecord(lx, ly, lz + 1, strength)); } while (spread.Count > 0) { LightRecord rec = spread.Dequeue(); int xi = rec.x + xdim; int zi = rec.z + zdim; IBoundedLitBlockCollection cc = chunkMap[xi / xdim, zi / zdim]; if (cc == null) { continue; } int x = xi % xdim; int y = rec.y; int z = zi % zdim; BlockInfo info = cc.GetInfo(x, y, z); int light = cc.GetSkyLight(x, y, z); int dimStr = Math.Max(rec.str - info.Opacity, 0); if (dimStr > light) { cc.SetSkyLight(x, y, z, dimStr); if (info.TransmitsLight) { strength = (info.Opacity > 0) ? dimStr : dimStr - 1; if (rec.y > 0) { if (heightMap[xi, zi] > rec.y - 1) { spread.Enqueue(new LightRecord(rec.x, rec.y - 1, rec.z, strength)); } else { spread.Enqueue(new LightRecord(rec.x, rec.y - 1, rec.z, dimStr)); } } if (rec.y < ydim - 1) { if (heightMap[xi, zi] > rec.y + 1) { spread.Enqueue(new LightRecord(rec.x, rec.y + 1, rec.z, strength)); } } if (heightMap[xi - 1, zi] > rec.y) { spread.Enqueue(new LightRecord(rec.x - 1, rec.y, rec.z, strength)); } if (heightMap[xi + 1, zi] > rec.y) { spread.Enqueue(new LightRecord(rec.x + 1, rec.y, rec.z, strength)); } if (heightMap[xi, zi - 1] > rec.y) { spread.Enqueue(new LightRecord(rec.x, rec.y, rec.z - 1, strength)); } if (heightMap[xi, zi + 1] > rec.y) { spread.Enqueue(new LightRecord(rec.x, rec.y, rec.z + 1, strength)); } } } } } private int LightBitmapIndex (BlockKey key) { int x = key.x + _xdim; int y = key.y; int z = key.z + _zdim; int zstride = _ydim; int xstride = _zdim * 3 * zstride; return (x * xstride) + (z * zstride) + y; } private void QueueRelight (BlockKey key) { if (key.x < -15 || key.x >= 31 || key.z < -15 || key.z >= 31) { return; } int index = LightBitmapIndex(key); if (!_lightbit[index]) { _lightbit[index] = true; _update.Enqueue(key); } } private IBoundedLitBlockCollection LocalChunk (int lx, int ly, int lz) { if (ly < 0 || ly >= _ydim) { return null; } if (lx < 0) { if (lz < 0) { return OnResolveNeighbor(-1, 0, -1); } else if (lz >= _zdim) { return OnResolveNeighbor(-1, 0, 1); } return OnResolveNeighbor(-1, 0, 0); } else if (lx >= _xdim) { if (lz < 0) { return OnResolveNeighbor(1, 0, -1); } else if (lz >= _zdim) { return OnResolveNeighbor(1, 0, 1); } return OnResolveNeighbor(1, 0, 0); } else { if (lz < 0) { return OnResolveNeighbor(0, 0, -1); } else if (lz >= _zdim) { return OnResolveNeighbor(0, 0, 1); } return _blockset; } } private int NeighborLight (IBoundedLitBlockCollection[,] chunkMap, int x, int y, int z) { if (y < 0 || y >= _ydim) { return 0; } int xdim = _xdim; int zdim = _zdim; int xi = x + xdim; int zi = z + zdim; IBoundedLitBlockCollection src = chunkMap[xi / xdim, zi / zdim]; if (src == null) { return 0; } x = xi % xdim; z = zi % zdim; BlockInfo info = src.GetInfo(x, y, z); if (!info.TransmitsLight) { return info.Luminance; } int light = src.GetBlockLight(x, y, z); return Math.Max((info.Opacity > 0) ? light : light - 1, info.Luminance - 1); } private int NeighborSkyLight (IBoundedLitBlockCollection[,] chunkMap, int x, int y, int z) { if (y < 0 || y >= _ydim) { return 0; } int xdim = _xdim; int zdim = _zdim; int xi = x + xdim; int zi = z + zdim; IBoundedLitBlockCollection src = chunkMap[xi / xdim, zi / zdim]; if (src == null) { return 0; } x = xi % xdim; z = zi % zdim; BlockInfo info = src.GetInfo(x, y, z); if (!info.TransmitsLight) { return BlockInfo.MIN_LUMINANCE; } int light = src.GetSkyLight(x, y, z); return (info.Opacity > 0) ? light : light - 1; } private int NeighborHeight (int x, int z) { IBoundedLitBlockCollection src = LocalChunk(x, 0, z); if (src == null) { return _ydim - 1; } x = (x + _xdim * 2) % _xdim; z = (z + _zdim * 2) % _zdim; return src.GetHeight(x, z); } private void TestBlockLight (IBoundedLitBlockCollection chunk, int x1, int y1, int z1, int x2, int y2, int z2) { int light1 = _blockset.GetBlockLight(x1, y1, z1); int light2 = chunk.GetBlockLight(x2, y2, z2); int lum1 = _blockset.GetInfo(x1, y1, z1).Luminance; int lum2 = chunk.GetInfo(x2, y2, z2).Luminance; int v1 = Math.Max(light1, lum1); int v2 = Math.Max(light2, lum2); if (Math.Abs(v1 - v2) > 1) { QueueRelight(new BlockKey(x1, y1, z1)); } } private void TestSkyLight (IBoundedLitBlockCollection chunk, int x1, int y1, int z1, int x2, int y2, int z2) { int light1 = _blockset.GetSkyLight(x1, y1, z1); int light2 = chunk.GetSkyLight(x2, y2, z2); if (Math.Abs(light1 - light2) > 1) { QueueRelight(new BlockKey(x1, y1, z1)); } } private IBoundedLitBlockCollection[,] LocalBlockLightMap () { IBoundedLitBlockCollection[,] map = new IBoundedLitBlockCollection[3, 3]; map[0, 0] = OnResolveNeighbor(-1, 0, -1); map[0, 1] = OnResolveNeighbor(-1, 0, 0); map[0, 2] = OnResolveNeighbor(-1, 0, 1); map[1, 0] = OnResolveNeighbor(0, 0, -1); map[1, 1] = _blockset; map[1, 2] = OnResolveNeighbor(0, 0, 1); map[2, 0] = OnResolveNeighbor(1, 0, -1); map[2, 1] = OnResolveNeighbor(1, 0, 0); map[2, 2] = OnResolveNeighbor(1, 0, 1); return map; } private int[,] LocalHeightMap (IBoundedLitBlockCollection[,] chunkMap) { int xdim = _xdim; int zdim = _zdim; int[,] map = new int[3 * xdim, 3 * zdim]; for (int xi = 0; xi < 3; xi++) { int xoff = xi * xdim; for (int zi = 0; zi < 3; zi++) { int zoff = zi * zdim; if (chunkMap[xi, zi] == null) { continue; } for (int x = 0; x < xdim; x++) { int xx = xoff + x; for (int z = 0; z < zdim; z++) { int zz = zoff + z; map[xx, zz] = chunkMap[xi, zi].GetHeight(x, z); } } } } return map; } private IBoundedLitBlockCollection OnResolveNeighbor (int relX, int relY, int relZ) { if (ResolveNeighbor != null) { IBoundedLitBlockCollection n = ResolveNeighbor(relX, relY, relZ); if (n == null) { return null; } if (n.XDim != _xdim || n.YDim != _ydim || n.ZDim != _zdim) { throw new InvalidOperationException("Subscriber returned incompatible ILitBlockCollection"); } return n; } return null; } } }