diff options
author | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
---|---|---|
committer | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
commit | abef6da56913f1c55528103e60a50451a39628b1 (patch) | |
tree | b3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/Lighting.c |
initial commit
Diffstat (limited to 'src/Lighting.c')
-rw-r--r-- | src/Lighting.c | 475 |
1 files changed, 475 insertions, 0 deletions
diff --git a/src/Lighting.c b/src/Lighting.c new file mode 100644 index 0000000..3800d80 --- /dev/null +++ b/src/Lighting.c @@ -0,0 +1,475 @@ +#include "Lighting.h" +#include "Block.h" +#include "Funcs.h" +#include "MapRenderer.h" +#include "Platform.h" +#include "World.h" +#include "Logger.h" +#include "Event.h" +#include "Game.h" +#include "String.h" +#include "Chat.h" +#include "ExtMath.h" +#include "Options.h" +#include "Builder.h" + +const char* const LightingMode_Names[LIGHTING_MODE_COUNT] = { "Classic", "Fancy" }; + +cc_uint8 Lighting_Mode; +cc_bool Lighting_ModeLockedByServer; +cc_bool Lighting_ModeSetByServer; +cc_uint8 Lighting_ModeUserCached; +struct _Lighting Lighting; +#define Lighting_Pack(x, z) ((x) + World.Width * (z)) + +void Lighting_SetMode(cc_uint8 mode, cc_bool fromServer) { + cc_uint8 oldMode = Lighting_Mode; + Lighting_Mode = mode; + + Event_RaiseLightingMode(&WorldEvents.LightingModeChanged, oldMode, fromServer); +} + + +/*########################################################################################################################* +*----------------------------------------------------Classic lighting-----------------------------------------------------* +*#########################################################################################################################*/ +static cc_int16* classic_heightmap; +#define HEIGHT_UNCALCULATED Int16_MaxValue + +#define ClassicLighting_CalcBody(get_block)\ +for (y = maxY; y >= 0; y--, i -= World.OneY) {\ + block = get_block;\ +\ + if (Blocks.BlocksLight[block]) {\ + offset = (Blocks.LightOffset[block] >> LIGHT_FLAG_SHADES_FROM_BELOW) & 1;\ + classic_heightmap[hIndex] = y - offset;\ + return y - offset;\ + }\ +} + +static int ClassicLighting_CalcHeightAt(int x, int maxY, int z, int hIndex) { + int i = World_Pack(x, maxY, z); + BlockID block; + int y, offset; + +#ifndef EXTENDED_BLOCKS + ClassicLighting_CalcBody(World.Blocks[i]); +#else + if (World.IDMask <= 0xFF) { + ClassicLighting_CalcBody(World.Blocks[i]); + } else { + ClassicLighting_CalcBody(World.Blocks[i] | (World.Blocks2[i] << 8)); + } +#endif + + classic_heightmap[hIndex] = -10; + return -10; +} + +int ClassicLighting_GetLightHeight(int x, int z) { + int hIndex = Lighting_Pack(x, z); + int lightH = classic_heightmap[hIndex]; + return lightH == HEIGHT_UNCALCULATED ? ClassicLighting_CalcHeightAt(x, World.Height - 1, z, hIndex) : lightH; +} + +/* Outside color is same as sunlight color, so we reuse when possible */ +cc_bool ClassicLighting_IsLit(int x, int y, int z) { + return y > ClassicLighting_GetLightHeight(x, z); +} + +cc_bool ClassicLighting_IsLit_Fast(int x, int y, int z) { + return y > classic_heightmap[Lighting_Pack(x, z)]; +} + +static PackedCol ClassicLighting_Color(int x, int y, int z) { + if (!World_Contains(x, y, z)) return Env.SunCol; + return y > ClassicLighting_GetLightHeight(x, z) ? Env.SunCol : Env.ShadowCol; +} + +static PackedCol SmoothLighting_Color(int x, int y, int z) { + if (!World_Contains(x, y, z)) return Env.SunCol; + if (Blocks.Brightness[World_GetBlock(x, y, z)]) return Env.SunCol; + return y > ClassicLighting_GetLightHeight(x, z) ? Env.SunCol : Env.ShadowCol; +} + +static PackedCol ClassicLighting_Color_XSide(int x, int y, int z) { + if (!World_Contains(x, y, z)) return Env.SunXSide; + return y > ClassicLighting_GetLightHeight(x, z) ? Env.SunXSide : Env.ShadowXSide; +} + +static PackedCol ClassicLighting_Color_Sprite_Fast(int x, int y, int z) { + return y > classic_heightmap[Lighting_Pack(x, z)] ? Env.SunCol : Env.ShadowCol; +} + +static PackedCol ClassicLighting_Color_YMax_Fast(int x, int y, int z) { + return y > classic_heightmap[Lighting_Pack(x, z)] ? Env.SunCol : Env.ShadowCol; +} + +static PackedCol ClassicLighting_Color_YMin_Fast(int x, int y, int z) { + return y > classic_heightmap[Lighting_Pack(x, z)] ? Env.SunYMin : Env.ShadowYMin; +} + +static PackedCol ClassicLighting_Color_XSide_Fast(int x, int y, int z) { + return y > classic_heightmap[Lighting_Pack(x, z)] ? Env.SunXSide : Env.ShadowXSide; +} + +static PackedCol ClassicLighting_Color_ZSide_Fast(int x, int y, int z) { + return y > classic_heightmap[Lighting_Pack(x, z)] ? Env.SunZSide : Env.ShadowZSide; +} + +void ClassicLighting_Refresh(void) { + int i; + for (i = 0; i < World.Width * World.Length; i++) { + classic_heightmap[i] = HEIGHT_UNCALCULATED; + } +} + + +/*########################################################################################################################* +*----------------------------------------------------Lighting update------------------------------------------------------* +*#########################################################################################################################*/ +static void ClassicLighting_UpdateLighting(int x, int y, int z, BlockID oldBlock, BlockID newBlock, int index, int lightH) { + cc_bool didBlock = Blocks.BlocksLight[oldBlock]; + cc_bool nowBlocks = Blocks.BlocksLight[newBlock]; + int oldOffset = (Blocks.LightOffset[oldBlock] >> LIGHT_FLAG_SHADES_FROM_BELOW) & 1; + int newOffset = (Blocks.LightOffset[newBlock] >> LIGHT_FLAG_SHADES_FROM_BELOW) & 1; + BlockID above; + + /* 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 */ + } + + if ((y - newOffset) >= lightH) { + if (nowBlocks) { + classic_heightmap[index] = 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 block Y was above or equal to old light height, then the new light height must be <= block Y */ + ClassicLighting_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. */ + above = y == (World.Height - 1) ? BLOCK_AIR : World_GetBlock(x, y + 1, z); + if (Blocks.BlocksLight[above]) return; + + if (nowBlocks) { + classic_heightmap[index] = y - newOffset; + } else { + ClassicLighting_CalcHeightAt(x, y - 1, z, index); + } + } +} + +static cc_bool ClassicLighting_Needs(BlockID block, BlockID other) { + return Blocks.Draw[block] != DRAW_OPAQUE || Blocks.Draw[other] != DRAW_GAS; +} + +#define ClassicLighting_NeedsNeighourBody(get_block)\ +/* Update if any blocks in the chunk are affected by light change. */ \ +for (; y >= minY; y--, i -= World.OneY) {\ + other = get_block;\ + affected = y == nY ? ClassicLighting_Needs(block, other) : Blocks.Draw[other] != DRAW_GAS;\ + if (affected) return true;\ +} + +static cc_bool ClassicLighting_NeedsNeighour(BlockID block, int i, int minY, int y, int nY) { + BlockID other; + cc_bool affected; + +#ifndef EXTENDED_BLOCKS + ClassicLighting_NeedsNeighourBody(World.Blocks[i]); +#else + if (World.IDMask <= 0xFF) { + ClassicLighting_NeedsNeighourBody(World.Blocks[i]); + } else { + ClassicLighting_NeedsNeighourBody(World.Blocks[i] | (World.Blocks2[i] << 8)); + } +#endif + return false; +} + +static void ClassicLighting_ResetNeighbour(int x, int y, int z, BlockID block, int cx, int cy, int cz, int minCy, int maxCy) { + int minY, maxY; + + if (minCy == maxCy) { + minY = cy << CHUNK_SHIFT; + + if (ClassicLighting_NeedsNeighour(block, World_Pack(x, y, z), minY, y, y)) { + MapRenderer_RefreshChunk(cx, cy, cz); + } + } else { + for (cy = maxCy; cy >= minCy; cy--) { + minY = (cy << CHUNK_SHIFT); + maxY = (cy << CHUNK_SHIFT) + CHUNK_MAX; + if (maxY > World.MaxY) maxY = World.MaxY; + + if (ClassicLighting_NeedsNeighour(block, World_Pack(x, maxY, z), minY, maxY, y)) { + MapRenderer_RefreshChunk(cx, cy, cz); + } + } + } +} + +static void ClassicLighting_ResetColumn(int cx, int cy, int cz, int minCy, int maxCy) { + if (minCy == maxCy) { + MapRenderer_RefreshChunk(cx, cy, cz); + } else { + for (cy = maxCy; cy >= minCy; cy--) { + MapRenderer_RefreshChunk(cx, cy, cz); + } + } +} + +static void ClassicLighting_RefreshAffected(int x, int y, int z, BlockID block, int oldHeight, int newHeight) { + int cx = x >> CHUNK_SHIFT, bX = x & CHUNK_MASK; + int cy = y >> CHUNK_SHIFT, bY = y & CHUNK_MASK; + int cz = z >> CHUNK_SHIFT, bZ = z & CHUNK_MASK; + + /* NOTE: much 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 = min(oldCy, newCy), maxCy = max(oldCy, newCy); + ClassicLighting_ResetColumn(cx, cy, cz, minCy, maxCy); + + if (bX == 0 && cx > 0) { + ClassicLighting_ResetNeighbour(x - 1, y, z, block, cx - 1, cy, cz, minCy, maxCy); + } + if (bY == 0 && cy > 0 && ClassicLighting_Needs(block, World_GetBlock(x, y - 1, z))) { + MapRenderer_RefreshChunk(cx, cy - 1, cz); + } + if (bZ == 0 && cz > 0) { + ClassicLighting_ResetNeighbour(x, y, z - 1, block, cx, cy, cz - 1, minCy, maxCy); + } + + if (bX == 15 && cx < World.ChunksX - 1) { + ClassicLighting_ResetNeighbour(x + 1, y, z, block, cx + 1, cy, cz, minCy, maxCy); + } + if (bY == 15 && cy < World.ChunksY - 1 && ClassicLighting_Needs(block, World_GetBlock(x, y + 1, z))) { + MapRenderer_RefreshChunk(cx, cy + 1, cz); + } + if (bZ == 15 && cz < World.ChunksZ - 1) { + ClassicLighting_ResetNeighbour(x, y, z + 1, block, cx, cy, cz + 1, minCy, maxCy); + } +} + +void ClassicLighting_OnBlockChanged(int x, int y, int z, BlockID oldBlock, BlockID newBlock) { + int hIndex = Lighting_Pack(x, z); + int lightH = classic_heightmap[hIndex]; + int newHeight; + + /* 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 == HEIGHT_UNCALCULATED) return; + + ClassicLighting_UpdateLighting(x, y, z, oldBlock, newBlock, hIndex, lightH); + newHeight = classic_heightmap[hIndex] + 1; + ClassicLighting_RefreshAffected(x, y, z, newBlock, lightH + 1, newHeight); +} + + +/*########################################################################################################################* +*---------------------------------------------------Lighting heightmap----------------------------------------------------* +*#########################################################################################################################*/ +static int Heightmap_InitialCoverage(int x1, int z1, int xCount, int zCount, int* skip) { + int elemsLeft = 0, index = 0, curRunCount = 0; + int x, z, hIndex, lightH; + + for (z = 0; z < zCount; z++) { + hIndex = Lighting_Pack(x1, z1 + z); + for (x = 0; x < xCount; x++) { + lightH = classic_heightmap[hIndex++]; + + skip[index] = 0; + if (lightH == HEIGHT_UNCALCULATED) { + elemsLeft++; + curRunCount = 0; + } else { + skip[index - curRunCount]++; + curRunCount++; + } + index++; + } + curRunCount = 0; /* We can only skip an entire X row at most. */ + } + return elemsLeft; +} + +#define Heightmap_CalculateBody(get_block)\ +for (y = World.Height - 1; y >= 0; y--) {\ + if (elemsLeft <= 0) { return true; } \ + mapIndex = World_Pack(x1, y, z1);\ + hIndex = Lighting_Pack(x1, z1);\ +\ + for (z = 0; z < zCount; z++) {\ + baseIndex = mapIndex;\ + index = z * xCount;\ + for (x = 0; x < xCount;) {\ + curRunCount = skip[index];\ + x += curRunCount; mapIndex += curRunCount; index += curRunCount;\ +\ + if (x < xCount && Blocks.BlocksLight[get_block]) {\ + lightOffset = (Blocks.LightOffset[get_block] >> LIGHT_FLAG_SHADES_FROM_BELOW) & 1;\ + classic_heightmap[hIndex + x] = (cc_int16)(y - lightOffset);\ + elemsLeft--;\ + skip[index] = 0;\ +\ + offset = prevRunCount + curRunCount;\ + newRunCount = skip[index - offset] + 1;\ +\ + /* consider case 1 0 1 0, where we are at last 0 */ \ + /* we need to make this 3 0 0 0 and advance by 1 */ \ + oldRunCount = (x - offset + newRunCount) < xCount ? skip[index - offset + newRunCount] : 0; \ + if (oldRunCount != 0) {\ + skip[index - offset + newRunCount] = 0; \ + newRunCount += oldRunCount; \ + } \ + skip[index - offset] = newRunCount; \ + x += oldRunCount; index += oldRunCount; mapIndex += oldRunCount; \ + prevRunCount = newRunCount; \ + } else { \ + prevRunCount = 0; \ + }\ + x++; mapIndex++; index++; \ + }\ + prevRunCount = 0;\ + hIndex += World.Width;\ + mapIndex = baseIndex + World.Width; /* advance one Z */ \ + }\ +} + +static cc_bool Heightmap_CalculateCoverage(int x1, int z1, int xCount, int zCount, int elemsLeft, int* skip) { + int prevRunCount = 0, curRunCount, newRunCount, oldRunCount; + int lightOffset, offset; + int mapIndex, hIndex, baseIndex, index; + int x, y, z; + +#ifndef EXTENDED_BLOCKS + Heightmap_CalculateBody(World.Blocks[mapIndex]); +#else + if (World.IDMask <= 0xFF) { + Heightmap_CalculateBody(World.Blocks[mapIndex]); + } else { + Heightmap_CalculateBody(World.Blocks[mapIndex] | (World.Blocks2[mapIndex] << 8)); + } +#endif + return false; +} + +static void Heightmap_FinishCoverage(int x1, int z1, int xCount, int zCount) { + int x, z, hIndex, lightH; + + for (z = 0; z < zCount; z++) { + hIndex = Lighting_Pack(x1, z1 + z); + for (x = 0; x < xCount; x++, hIndex++) { + lightH = classic_heightmap[hIndex]; + + if (lightH == HEIGHT_UNCALCULATED) { + classic_heightmap[hIndex] = -10; + } + } + } +} + + +void ClassicLighting_LightHint(int startX, int startY, int startZ) { + int x1 = max(startX, 0), x2 = min(World.Width, startX + EXTCHUNK_SIZE); + int z1 = max(startZ, 0), z2 = min(World.Length, startZ + EXTCHUNK_SIZE); + int xCount = x2 - x1, zCount = z2 - z1; + int skip[EXTCHUNK_SIZE * EXTCHUNK_SIZE]; + + int elemsLeft = Heightmap_InitialCoverage(x1, z1, xCount, zCount, skip); + if (!Heightmap_CalculateCoverage(x1, z1, xCount, zCount, elemsLeft, skip)) { + Heightmap_FinishCoverage(x1, z1, xCount, zCount); + } +} + +void ClassicLighting_FreeState(void) { + Mem_Free(classic_heightmap); + classic_heightmap = NULL; +} + +void ClassicLighting_AllocState(void) { + classic_heightmap = (cc_int16*)Mem_TryAlloc(World.Width * World.Length, 2); + if (classic_heightmap) { + ClassicLighting_Refresh(); + } else { + World_OutOfMemory(); + } +} + +static void ClassicLighting_SetActive(void) { + cc_bool smoothLighting = false; + if (!Game_ClassicMode) smoothLighting = Options_GetBool(OPT_SMOOTH_LIGHTING, false); + + Lighting.OnBlockChanged = ClassicLighting_OnBlockChanged; + Lighting.Refresh = ClassicLighting_Refresh; + Lighting.IsLit = ClassicLighting_IsLit; + Lighting.Color = smoothLighting ? SmoothLighting_Color : ClassicLighting_Color; + Lighting.Color_XSide = ClassicLighting_Color_XSide; + + Lighting.IsLit_Fast = ClassicLighting_IsLit_Fast; + Lighting.Color_Sprite_Fast = ClassicLighting_Color_Sprite_Fast; + Lighting.Color_YMax_Fast = ClassicLighting_Color_YMax_Fast; + Lighting.Color_YMin_Fast = ClassicLighting_Color_YMin_Fast; + Lighting.Color_XSide_Fast = ClassicLighting_Color_XSide_Fast; + Lighting.Color_ZSide_Fast = ClassicLighting_Color_ZSide_Fast; + + Lighting.FreeState = ClassicLighting_FreeState; + Lighting.AllocState = ClassicLighting_AllocState; + Lighting.LightHint = ClassicLighting_LightHint; +} + + +/*########################################################################################################################* +*---------------------------------------------------Lighting component----------------------------------------------------* +*#########################################################################################################################*/ +static void Lighting_ApplyActive(void) { + if (Lighting_Mode != LIGHTING_MODE_CLASSIC) { + FancyLighting_SetActive(); + } else { + ClassicLighting_SetActive(); + } +} + +static void Lighting_SwitchActive(void) { + Lighting.FreeState(); + Lighting_ApplyActive(); + Lighting.AllocState(); +} + +static void Lighting_HandleModeChanged(void* obj, cc_uint8 oldMode, cc_bool fromServer) { + if (Lighting_Mode == oldMode) return; + Builder_ApplyActive(); + + if (World.Loaded) { + Lighting_SwitchActive(); + MapRenderer_Refresh(); + } else { + Lighting_ApplyActive(); + } +} + +static void OnInit(void) { + Lighting_Mode = Options_GetEnum(OPT_LIGHTING_MODE, LIGHTING_MODE_CLASSIC, LightingMode_Names, LIGHTING_MODE_COUNT); + Lighting_ModeLockedByServer = false; + Lighting_ModeSetByServer = false; + Lighting_ModeUserCached = Lighting_Mode; + + FancyLighting_OnInit(); + Lighting_ApplyActive(); + + Event_Register_(&WorldEvents.LightingModeChanged, NULL, Lighting_HandleModeChanged); +} +static void OnReset(void) { Lighting.FreeState(); } +static void OnNewMapLoaded(void) { Lighting.AllocState(); } + +struct IGameComponent Lighting_Component = { + OnInit, /* Init */ + OnReset, /* Free */ + OnReset, /* Reset */ + OnReset, /* OnNewMap */ + OnNewMapLoaded /* OnNewMapLoaded */ +}; \ No newline at end of file |