mirror of
https://github.com/ClassiCube/ClassiCube.git
synced 2025-09-24 05:10:42 -04:00
Modularise lighting more
This commit is contained in:
parent
08d38fd348
commit
747e3183d6
@ -187,6 +187,7 @@
|
||||
<Compile Include="Map\Formats\MapSchematic.Exporter.cs" />
|
||||
<Compile Include="Map\Formats\MapLvl.Importer.cs" />
|
||||
<Compile Include="Map\Formats\NbtFile.cs" />
|
||||
<Compile Include="Map\Lighting\BasicLighting.Updater.cs" />
|
||||
<Compile Include="Map\Lighting\IWorldLighting.cs" />
|
||||
<Compile Include="Map\Lighting\BasicLighting.cs" />
|
||||
<Compile Include="Map\Lighting\BasicLighting.Heightmap.cs" />
|
||||
|
@ -283,17 +283,18 @@ namespace ClassicalSharp {
|
||||
}
|
||||
|
||||
public void UpdateBlock(int x, int y, int z, BlockID block) {
|
||||
int oldHeight = Lighting.GetLightHeight(x, z) + 1;
|
||||
BlockID oldBlock = World.GetBlock(x, y, z);
|
||||
World.SetBlock(x, y, z, block);
|
||||
|
||||
Lighting.UpdateLight(x, y, z, oldBlock, block);
|
||||
WeatherRenderer weather = WeatherRenderer;
|
||||
if (weather.heightmap != null && !World.IsNotLoaded)
|
||||
weather.OnBlockChanged(x, y, z, oldBlock, block);
|
||||
Lighting.OnBlockChanged(x, y, z, oldBlock, block);
|
||||
|
||||
int newHeight = Lighting.GetLightHeight(x, z) + 1;
|
||||
MapRenderer.RedrawBlock(x, y, z, block, oldHeight, newHeight);
|
||||
// Refresh the chunk the block was located in.
|
||||
int cx = x >> 4, cy = y >> 4, cz = z >> 4;
|
||||
MapRenderer.GetChunk(cx, cy, cz).AllAir &= BlockInfo.Draw[block] == DrawType.Gas;
|
||||
MapRenderer.RefreshChunk(cx, cy, cz);
|
||||
}
|
||||
|
||||
float limitMilliseconds;
|
||||
|
130
ClassicalSharp/Map/Lighting/BasicLighting.Updater.cs
Normal file
130
ClassicalSharp/Map/Lighting/BasicLighting.Updater.cs
Normal file
@ -0,0 +1,130 @@
|
||||
// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3
|
||||
using System;
|
||||
using ClassicalSharp.Renderers;
|
||||
|
||||
#if USE16_BIT
|
||||
using BlockID = System.UInt16;
|
||||
#else
|
||||
using BlockID = System.Byte;
|
||||
#endif
|
||||
|
||||
namespace ClassicalSharp.Map {
|
||||
|
||||
/// <summary> Manages lighting through a simple heightmap, where each block is either in sun or shadow. </summary>
|
||||
public sealed partial class BasicLighting : IWorldLighting {
|
||||
|
||||
public override void OnBlockChanged(int x, int y, int z, BlockID oldBlock, BlockID newBlock) {
|
||||
bool didBlock = info.BlocksLight[oldBlock];
|
||||
bool nowBlocks = info.BlocksLight[newBlock];
|
||||
int oldOffset = (info.LightOffset[oldBlock] >> Side.Top) & 1;
|
||||
int newOffset = (info.LightOffset[newBlock] >> Side.Top) & 1;
|
||||
|
||||
// Two cases we need to handle here:
|
||||
if (didBlock == nowBlocks) {
|
||||
if (!didBlock) return; // a) both old and new block do not block light
|
||||
if (oldOffset == newOffset) return; // b) both blocks blocked light at the same Y coordinate
|
||||
}
|
||||
|
||||
int index = (z * width) + x;
|
||||
int lightH = heightmap[index];
|
||||
// Since light wasn't checked to begin with, means column never had meshes for any of its chunks built.
|
||||
// So we don't need to do anything.
|
||||
if (lightH == short.MaxValue) return;
|
||||
|
||||
int oldHeight = lightH + 1;
|
||||
if ((y - newOffset) >= lightH) {
|
||||
if (nowBlocks) {
|
||||
heightmap[index] = (short)(y - newOffset);
|
||||
} else {
|
||||
// Part of the column is now visible to light, we don't know how exactly how high it should be though.
|
||||
// However, we know that if the old block was above or equal to light height, then the new light height must be <= old block.y
|
||||
CalcHeightAt(x, y, z, index);
|
||||
}
|
||||
} else if (y == lightH && oldOffset == 0) {
|
||||
// For a solid block on top of an upside down slab, they will both have the same light height.
|
||||
// So we need to account for this particular case.
|
||||
BlockID above = y == (height - 1) ? Block.Air : game.World.GetBlock(x, y + 1, z);
|
||||
if (info.BlocksLight[above]) return;
|
||||
|
||||
if (nowBlocks) {
|
||||
heightmap[index] = (short)(y - newOffset);
|
||||
} else {
|
||||
CalcHeightAt(x, y - 1, z, index);
|
||||
}
|
||||
}
|
||||
int newHeight = heightmap[index] + 1;
|
||||
RefreshAffected(x, y, z, newBlock, oldHeight, newHeight);
|
||||
}
|
||||
|
||||
MapRenderer renderer;
|
||||
void RefreshAffected(int x, int y, int z, BlockID block, int oldHeight, int newHeight) {
|
||||
int cx = x >> 4, cy = y >> 4, cz = z >> 4;
|
||||
renderer = game.MapRenderer;
|
||||
|
||||
// NOTE: It's a lot faster to only update the chunks that are affected by the change in shadows,
|
||||
// rather than the entire column.
|
||||
int newCy = newHeight < 0 ? 0 : newHeight >> 4;
|
||||
int oldCy = oldHeight < 0 ? 0 : oldHeight >> 4;
|
||||
int minCy = Math.Min(oldCy, newCy), maxCy = Math.Max(oldCy, newCy);
|
||||
ResetColumn(cx, cy, cz, minCy, maxCy);
|
||||
World world = game.World;
|
||||
|
||||
int bX = x & 0x0F, bY = y & 0x0F, bZ = z & 0x0F;
|
||||
if (bX == 0 && cx > 0)
|
||||
ResetNeighbour(x - 1, y, z, block, cx - 1, cy, cz, minCy, maxCy);
|
||||
if (bY == 0 && cy > 0 && Needs(block, world.GetBlock(x, y - 1, z)))
|
||||
renderer.RefreshChunk(cx, cy - 1, cz);
|
||||
if (bZ == 0 && cz > 0)
|
||||
ResetNeighbour(x, y, z - 1, block, cx, cy, cz - 1, minCy, maxCy);
|
||||
|
||||
if (bX == 15 && cx < renderer.chunksX - 1)
|
||||
ResetNeighbour(x + 1, y, z, block, cx + 1, cy, cz, minCy, maxCy);
|
||||
if (bY == 15 && cy < renderer.chunksY - 1 && Needs(block, world.GetBlock(x, y + 1, z)))
|
||||
renderer.RefreshChunk(cx, cy + 1, cz);
|
||||
if (bZ == 15 && cz < renderer.chunksZ - 1)
|
||||
ResetNeighbour(x, y, z + 1, block, cx, cy, cz + 1, minCy, maxCy);
|
||||
}
|
||||
|
||||
bool Needs(BlockID block, BlockID other) {
|
||||
return info.Draw[block] != DrawType.Opaque || info.Draw[other] != DrawType.Gas;
|
||||
}
|
||||
|
||||
void ResetNeighbour(int x, int y, int z, BlockID block,
|
||||
int cx, int cy, int cz, int minCy, int maxCy) {
|
||||
World world = game.World;
|
||||
if (minCy == maxCy) {
|
||||
int index = x + world.Width * (z + y * world.Length);
|
||||
ResetNeighourChunk(cx, cy, cz, block, y, index, y);
|
||||
} else {
|
||||
for (cy = maxCy; cy >= minCy; cy--) {
|
||||
int maxY = Math.Min(world.Height - 1, (cy << 4) + 15);
|
||||
int index = x + world.Width * (z + maxY * world.Length);
|
||||
ResetNeighourChunk(cx, cy, cz, block, maxY, index, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ResetNeighourChunk(int cx, int cy, int cz, BlockID block,
|
||||
int y, int index, int nY) {
|
||||
World world = game.World;
|
||||
int minY = cy << 4;
|
||||
|
||||
// Update if any blocks in the chunk are affected by light change
|
||||
for (; y >= minY; y--) {
|
||||
BlockID other = world.blocks[index];
|
||||
bool affected = y == nY ? Needs(block, other) : info.Draw[other] != DrawType.Gas;
|
||||
if (affected) { renderer.RefreshChunk(cx, cy, cz); return; }
|
||||
index -= world.Width * world.Length;
|
||||
}
|
||||
}
|
||||
|
||||
void ResetColumn(int cx, int cy, int cz, int minCy, int maxCy) {
|
||||
if (minCy == maxCy) {
|
||||
renderer.RefreshChunk(cx, cy, cz);
|
||||
} else {
|
||||
for (cy = maxCy; cy >= minCy; cy--)
|
||||
renderer.RefreshChunk(cx, cy, cz);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -80,7 +80,7 @@ namespace ClassicalSharp.Map {
|
||||
}
|
||||
}
|
||||
|
||||
public override int GetLightHeight(int x, int z) {
|
||||
int GetLightHeight(int x, int z) {
|
||||
int index = (z * width) + x;
|
||||
int lightH = heightmap[index];
|
||||
return lightH == short.MaxValue ? CalcHeightAt(x, height - 1, z, index) : lightH;
|
||||
@ -126,45 +126,5 @@ namespace ClassicalSharp.Map {
|
||||
for (int i = 0; i < heightmap.Length; i++)
|
||||
heightmap[i] = short.MaxValue;
|
||||
}
|
||||
|
||||
public override void UpdateLight(int x, int y, int z, BlockID oldBlock, BlockID newBlock) {
|
||||
bool didBlock = info.BlocksLight[oldBlock];
|
||||
bool nowBlocks = info.BlocksLight[newBlock];
|
||||
int oldOffset = (info.LightOffset[oldBlock] >> Side.Top) & 1;
|
||||
int newOffset = (info.LightOffset[newBlock] >> Side.Top) & 1;
|
||||
|
||||
// Two cases we need to handle here:
|
||||
if (didBlock == nowBlocks) {
|
||||
if (!didBlock) return; // a) both old and new block do not block light
|
||||
if (oldOffset == newOffset) return; // b) both blocks blocked light at the same Y coordinate
|
||||
}
|
||||
|
||||
int index = (z * width) + x;
|
||||
int lightH = heightmap[index];
|
||||
if (lightH == short.MaxValue) {
|
||||
// We have to calculate the entire column for visibility, because the old/new block info is
|
||||
// useless if there is another block higher than block.y that blocks sunlight.
|
||||
CalcHeightAt(x, height - 1, z, index);
|
||||
} else if ((y - newOffset) >= lightH) {
|
||||
if (nowBlocks) {
|
||||
heightmap[index] = (short)(y - newOffset);
|
||||
} else {
|
||||
// Part of the column is now visible to light, we don't know how exactly how high it should be though.
|
||||
// However, we know that if the old block was above or equal to light height, then the new light height must be <= old block.y
|
||||
CalcHeightAt(x, y, z, index);
|
||||
}
|
||||
} else if (y == lightH && oldOffset == 0) {
|
||||
// For a solid block on top of an upside down slab, they will both have the same light height.
|
||||
// So we need to account for this particular case.
|
||||
BlockID above = y == (height - 1) ? Block.Air : game.World.GetBlock(x, y + 1, z);
|
||||
if (info.BlocksLight[above]) return;
|
||||
|
||||
if (nowBlocks) {
|
||||
heightmap[index] = (short)(y - newOffset);
|
||||
} else {
|
||||
CalcHeightAt(x, y - 1, z, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,13 +24,10 @@ namespace ClassicalSharp.Map {
|
||||
// Except this function is a lot more optimised and minimises cache misses.
|
||||
public unsafe abstract void LightHint(int startX, int startZ, BlockID* mapPtr);
|
||||
|
||||
/// <summary> Returns the y coordinate of the highest block that is fully not in sunlight. </summary>
|
||||
/// <remarks> *** Does NOT check that the coordinates are inside the map. *** <br/>
|
||||
/// e.g. if cobblestone was at y = 5, this method would return 4. </remarks>
|
||||
public abstract int GetLightHeight(int x, int z);
|
||||
|
||||
/// <summary> Updates the lighting for the block at that position, which may in turn affect other blocks. </summary>
|
||||
public abstract void UpdateLight(int x, int y, int z, BlockID oldBlock, BlockID newBlock);
|
||||
/// <summary> Called when a block is changed, to update the lighting information. </summary>
|
||||
/// <remarks> Derived classes ***MUST*** mark all chunks affected by this lighting change
|
||||
/// as needing to be refreshed. </remarks>
|
||||
public abstract void OnBlockChanged(int x, int y, int z, BlockID oldBlock, BlockID newBlock);
|
||||
|
||||
/// <summary> Discards all cached lighting information. </summary>
|
||||
public virtual void Refresh() { }
|
||||
|
@ -71,7 +71,7 @@ namespace ClassicalSharp.Map {
|
||||
/// <summary> Sets the block at the given world coordinates without bounds checking,
|
||||
/// and also recalculates the heightmap for the given (x,z) column. </summary>
|
||||
public void SetBlock(Vector3I p, BlockID blockId) {
|
||||
SetBlock(p.X, p.Y, p.Z, blockId);
|
||||
blocks[(p.Y * Length + p.Z) * Width + p.X] = blockId;
|
||||
}
|
||||
|
||||
/// <summary> Returns the block at the given world coordinates without bounds checking. </summary>
|
||||
|
@ -68,6 +68,7 @@ namespace ClassicalSharp.Network.Protocols {
|
||||
byte HandleDefineBlockCommonStart(NetReader reader, bool uniqueSideTexs) {
|
||||
byte id = reader.ReadUInt8();
|
||||
BlockInfo info = game.BlockInfo;
|
||||
bool didBlockLight = info.BlocksLight[id];
|
||||
info.ResetBlockProps(id);
|
||||
|
||||
info.Name[id] = reader.ReadString();
|
||||
@ -86,7 +87,6 @@ namespace ClassicalSharp.Network.Protocols {
|
||||
info.SetTex(reader.ReadUInt8(), Side.Bottom, id);
|
||||
|
||||
// Need to refresh lighting when a block's light blocking state changes
|
||||
bool didBlockLight = info.BlocksLight[id];
|
||||
info.BlocksLight[id] = reader.ReadUInt8() == 0;
|
||||
if (!game.World.IsNotLoaded && (didBlockLight != info.BlocksLight[id])) {
|
||||
game.Lighting.Refresh();
|
||||
|
@ -160,9 +160,10 @@ namespace ClassicalSharp.Renderers {
|
||||
width = NextMultipleOf16(game.World.Width);
|
||||
height = NextMultipleOf16(game.World.Height);
|
||||
length = NextMultipleOf16(game.World.Length);
|
||||
chunksX = width >> 4;
|
||||
chunksY = height >> 4;
|
||||
chunksZ = length >> 4;
|
||||
|
||||
chunksX = width >> 4; renderer.chunksX = chunksX;
|
||||
chunksY = height >> 4; renderer.chunksY = chunksY;
|
||||
chunksZ = length >> 4; renderer.chunksZ = chunksZ;
|
||||
|
||||
int count = chunksX * chunksY * chunksZ;
|
||||
if (renderer.chunks == null || renderer.chunks.Length != count) {
|
||||
@ -237,90 +238,6 @@ namespace ClassicalSharp.Renderers {
|
||||
void ContextRecreated() { Refresh(); }
|
||||
|
||||
|
||||
public void RedrawBlock(int x, int y, int z, BlockID block, int oldHeight, int newHeight) {
|
||||
int cx = x >> 4, cy = y >> 4, cz = z >> 4;
|
||||
|
||||
// Does this chunk now contain air?
|
||||
ChunkInfo curInfo = renderer.unsortedChunks[cx + chunksX * (cy + cz * chunksY)];
|
||||
curInfo.AllAir &= game.BlockInfo.Draw[block] == DrawType.Gas;
|
||||
|
||||
// NOTE: It's a lot faster to only update the chunks that are affected by the change in shadows,
|
||||
// rather than the entire column.
|
||||
int newCy = newHeight < 0 ? 0 : newHeight >> 4;
|
||||
int oldCy = oldHeight < 0 ? 0 : oldHeight >> 4;
|
||||
int minCy = Math.Min(oldCy, newCy), maxCy = Math.Max(oldCy, newCy);
|
||||
ResetColumn(cx, cy, cz, minCy, maxCy);
|
||||
World world = game.World;
|
||||
|
||||
int bX = x & 0x0F, bY = y & 0x0F, bZ = z & 0x0F;
|
||||
if (bX == 0 && cx > 0)
|
||||
ResetNeighbour(x - 1, y, z, block, cx - 1, cy, cz, minCy, maxCy);
|
||||
if (bY == 0 && cy > 0 && Needs(block, world.GetBlock(x, y - 1, z)))
|
||||
RefreshChunk(cx, cy - 1, cz);
|
||||
if (bZ == 0 && cz > 0)
|
||||
ResetNeighbour(x, y, z - 1, block, cx, cy, cz - 1, minCy, maxCy);
|
||||
|
||||
if (bX == 15 && cx < chunksX - 1)
|
||||
ResetNeighbour(x + 1, y, z, block, cx + 1, cy, cz, minCy, maxCy);
|
||||
if (bY == 15 && cy < chunksY - 1 && Needs(block, world.GetBlock(x, y + 1, z)))
|
||||
RefreshChunk(cx, cy + 1, cz);
|
||||
if (bZ == 15 && cz < chunksZ - 1)
|
||||
ResetNeighbour(x, y, z + 1, block, cx, cy, cz + 1, minCy, maxCy);
|
||||
}
|
||||
|
||||
bool Needs(BlockID block, BlockID other) {
|
||||
return info.Draw[block] != DrawType.Opaque || info.Draw[other] != DrawType.Gas;
|
||||
}
|
||||
|
||||
void ResetNeighbour(int x, int y, int z, BlockID block,
|
||||
int cx, int cy, int cz, int minCy, int maxCy) {
|
||||
World world = game.World;
|
||||
if (minCy == maxCy) {
|
||||
int index = x + world.Width * (z + y * world.Length);
|
||||
ResetNeighourChunk(cx, cy, cz, block, y, index, y);
|
||||
} else {
|
||||
for (cy = maxCy; cy >= minCy; cy--) {
|
||||
int maxY = Math.Min(world.Height - 1, (cy << 4) + 15);
|
||||
int index = x + world.Width * (z + maxY * world.Length);
|
||||
ResetNeighourChunk(cx, cy, cz, block, maxY, index, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ResetNeighourChunk(int cx, int cy, int cz, BlockID block,
|
||||
int y, int index, int nY) {
|
||||
World world = game.World;
|
||||
int minY = cy << 4;
|
||||
|
||||
// Update if any blocks in the chunk are affected by light change
|
||||
for (; y >= minY; y--) {
|
||||
BlockID other = world.blocks[index];
|
||||
bool affected = y == nY ? Needs(block, other) : info.Draw[other] != DrawType.Gas;
|
||||
if (affected) { RefreshChunk(cx, cy, cz); return; }
|
||||
index -= world.Width * world.Length;
|
||||
}
|
||||
}
|
||||
|
||||
void ResetColumn(int cx, int cy, int cz, int minCy, int maxCy) {
|
||||
if (minCy == maxCy) {
|
||||
RefreshChunk(cx, cy, cz);
|
||||
} else {
|
||||
for (cy = maxCy; cy >= minCy; cy--)
|
||||
RefreshChunk(cx, cy, cz);
|
||||
}
|
||||
}
|
||||
|
||||
public void RefreshChunk(int cx, int cy, int cz) {
|
||||
if (cx < 0 || cy < 0 || cz < 0 ||
|
||||
cx >= chunksX || cy >= chunksY || cz >= chunksZ) return;
|
||||
|
||||
ChunkInfo info = renderer.unsortedChunks[cx + chunksX * (cy + cz * chunksY)];
|
||||
if (info.AllAir) return; // do not recreate chunks completely air
|
||||
info.Empty = false;
|
||||
info.PendingDelete = true;
|
||||
}
|
||||
|
||||
|
||||
int chunksTarget = 12;
|
||||
const double targetTime = (1.0 / 30) + 0.01;
|
||||
public void UpdateChunks(double delta) {
|
||||
|
@ -191,10 +191,10 @@ namespace ClassicalSharp.Renderers {
|
||||
|
||||
int index = (x * length) + z;
|
||||
int height = heightmap[index];
|
||||
// Two cases can be skipped:
|
||||
// a) rain height hadn't been calculated to begin with
|
||||
// b) modified is below current rain height
|
||||
if (height == short.MaxValue || y < height) return;
|
||||
// Two cases can be skipped here:
|
||||
// a) rain height was not calculated to begin with (height is short.MaxValue)
|
||||
// b) changed y is below current calculated rain height
|
||||
if (y < height) return;
|
||||
|
||||
if (nowBlock) {
|
||||
// Simple case: Rest of column below is now not visible to rain.
|
||||
|
@ -46,7 +46,7 @@ namespace ClassicalSharp.Renderers {
|
||||
Game game;
|
||||
IGraphicsApi gfx;
|
||||
|
||||
internal int _1DUsed = -1;
|
||||
internal int _1DUsed = -1, chunksX, chunksY, chunksZ;
|
||||
internal int renderCount = 0;
|
||||
internal ChunkInfo[] chunks, renderChunks, unsortedChunks;
|
||||
internal bool[] usedTranslucent, usedNormal;
|
||||
@ -67,12 +67,21 @@ namespace ClassicalSharp.Renderers {
|
||||
/// <summary> Discards any built meshes for all chunks in the map.</summary>
|
||||
public void Refresh() { updater.Refresh(); }
|
||||
|
||||
public void RedrawBlock(int x, int y, int z, BlockID block, int oldHeight, int newHeight) {
|
||||
updater.RedrawBlock(x, y, z, block, oldHeight, newHeight);
|
||||
/// <summary> Retrieves the information for the given chunk. </summary>
|
||||
public ChunkInfo GetChunk(int cx, int cy, int cz) {
|
||||
return unsortedChunks[cx + chunksX * (cy + cz * chunksY)];
|
||||
}
|
||||
|
||||
/// <summary> Marks the given chunk as needing to be deleted. </summary>
|
||||
public void RefreshChunk(int cx, int cy, int cz) { updater.RefreshChunk(cx, cy, cz); }
|
||||
public void RefreshChunk(int cx, int cy, int cz) {
|
||||
if (cx < 0 || cy < 0 || cz < 0 ||
|
||||
cx >= chunksX || cy >= chunksY || cz >= chunksZ) return;
|
||||
|
||||
ChunkInfo info = unsortedChunks[cx + chunksX * (cy + cz * chunksY)];
|
||||
if (info.AllAir) return; // do not recreate chunks completely air
|
||||
info.Empty = false;
|
||||
info.PendingDelete = true;
|
||||
}
|
||||
|
||||
/// <summary> Potentially generates meshes for several pending chunks. </summary>
|
||||
public void Update(double deltaTime) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user