From 724310c47bcacfaf33c0d14ab60acd735977ee4f Mon Sep 17 00:00:00 2001 From: MichaelFisher1997 Date: Fri, 10 Jul 2026 03:56:40 +0100 Subject: [PATCH] refactor: remove legacy lighting paths --- assets/shaders/vulkan/terrain.frag | 96 +---- assets/shaders/vulkan/terrain.frag.spv | Bin 63740 -> 63108 bytes assets/shaders/vulkan/terrain.vert | 16 +- assets/shaders/vulkan/terrain.vert.spv | Bin 11144 -> 10500 bytes docs/lighting-architecture.md | 13 + docs/shaders/spirv-sizes.json | 4 +- modules/engine-ecs/src/systems/render.zig | 3 +- modules/engine-graphics/src/cloud_system.zig | 13 +- .../src/shadow_system_tests.zig | 27 -- modules/engine-graphics/src/shadow_tests.zig | 17 +- .../src/vulkan/pipeline_specialized.zig | 5 +- .../src/vulkan/rhi_render_state.zig | 2 +- .../src/vulkan/rhi_shadow_bridge.zig | 2 +- .../engine-graphics/src/wireframe_cube.zig | 1 - modules/engine-rhi/src/rhi_types.zig | 39 +- modules/engine-rhi/src/root.zig | 1 - modules/engine-ui/src/debug_frustum.zig | 2 +- modules/engine-ui/src/debug_menu.zig | 2 - modules/game-core/src/block_outline.zig | 2 +- modules/game-core/src/hand_renderer.zig | 8 +- modules/game-core/src/settings/data.zig | 6 - .../game-core/src/settings/json_presets.zig | 6 - modules/game-core/src/ui/session_hud.zig | 4 +- modules/game-ui/src/screens/world.zig | 8 - modules/game-ui/src/screens/world_debug.zig | 5 +- .../src/screens/world_frame_params.zig | 2 - modules/world-core/src/chunk.zig | 46 --- modules/world-core/src/light.zig | 14 - modules/world-core/src/light_fuzz_tests.zig | 12 - modules/world-core/src/packed_light_tests.zig | 35 -- modules/world-core/src/root.zig | 3 - modules/world-lod/src/lod_geometry.zig | 1 - .../world-meshing/src/meshing/boundary.zig | 20 - .../src/meshing/boundary_cross_tests.zig | 28 -- .../src/meshing/cross_mesher.zig | 18 +- .../src/meshing/custom_mesh_mesher.zig | 21 +- .../src/meshing/flat_quad_mesher.zig | 18 +- .../src/meshing/greedy_mesher.zig | 25 +- .../src/meshing/lighting_sampler.zig | 70 +--- .../src/meshing/quadric_simplifier.zig | 2 +- .../src/meshing/tall_cross_mesher.zig | 21 +- .../src/meshing/wall_attached_mesher.zig | 18 +- modules/world-meshing/src/root.zig | 2 +- modules/world-runtime/src/lighting_engine.zig | 7 +- modules/world-runtime/src/world.zig | 2 - .../world-runtime/src/world_facade_tests.zig | 2 +- modules/world-runtime/src/world_renderer.zig | 5 +- .../worldgen-common/src/lighting_computer.zig | 376 ++---------------- src/game/app.zig | 1 - src/world_inline_tests.zig | 15 +- 50 files changed, 120 insertions(+), 926 deletions(-) create mode 100644 docs/lighting-architecture.md diff --git a/assets/shaders/vulkan/terrain.frag b/assets/shaders/vulkan/terrain.frag index c47f0827..71a0530c 100644 --- a/assets/shaders/vulkan/terrain.frag +++ b/assets/shaders/vulkan/terrain.frag @@ -15,9 +15,8 @@ layout(location = 11) in float vAO; layout(location = 12) in vec4 vClipPosCurrent; layout(location = 13) in vec4 vClipPosPrev; layout(location = 14) in float vMaskRadius; -layout(location = 15) in float vEntranceBounce; -layout(location = 16) in vec2 vEntranceDir; -layout(location = 17) in float vLODFade; +layout(location = 15) in float vCloud; +layout(location = 16) in float vLODFade; layout(location = 0) out vec4 FragColor; @@ -58,7 +57,6 @@ const int DEBUG_DIRECT_KEY = 7; const int DEBUG_SKY_FILL = 8; const int DEBUG_BLOCK_LIGHT = 9; const int DEBUG_OUTDOOR_FACTOR = 10; -const int DEBUG_ENTRANCE_BOUNCE = 11; const int DEBUG_SKYLIGHT = 12; const int DEBUG_AMBIENT_OCCLUSION = 13; @@ -116,7 +114,7 @@ layout(set = 0, binding = 2) uniform ShadowUniforms { vec4 overlap_starts; vec4 shadow_texel_sizes; vec4 shadow_depth_spans; - vec4 shadow_params; // x = light size, y = inverse resolution + vec4 shadow_params; // y = inverse resolution vec4 fade_params; // x = fade start, y = configured shadow distance } shadows; @@ -162,25 +160,6 @@ float interleavedGradientNoise(vec2 fragCoord) { return fract(magic.z * fract(dot(fragCoord.xy, magic.xy))); } -// PCSS blocker search using Poisson disk for better spatial distribution. -// searchRadius is derived from light size and receiver depth in light-space. -float findBlocker(vec2 uv, float zReceiver, int layer, float searchRadius, mat2 rot) { - float blockerDepthSum = 0.0; - int numBlockers = 0; - // Use first 8 Poisson samples for blocker search (cheaper than full 16) - for (int i = 0; i < 8; i++) { - vec2 offset = (rot * poissonDisk16[i]) * searchRadius; - float depth = texture(uShadowMapsRegular, vec3(uv + offset, float(layer))).r; - // Reverse-Z: blockers have GREATER depth than receiver - if (depth > zReceiver + 0.0002) { - blockerDepthSum += depth; - numBlockers++; - } - } - if (numBlockers == 0) return -1.0; - return blockerDepthSum / float(numBlockers); -} - vec3 shadowProjCoords(vec3 fragPosWorld, int layer) { vec4 fragPosLightSpace = shadows.light_space_matrices[layer] * vec4(fragPosWorld, 1.0); vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; @@ -334,9 +313,6 @@ float computeShadowCascades(vec3 fragPosWorld, vec3 N, vec3 L, float cascadeDist const float MAX_ENV_MIP_LEVEL = 8.0; const float SUN_RADIANCE_TO_IRRADIANCE = 4.0; const float SUN_VOLUMETRIC_INTENSITY = 3.0; -const float LEGACY_LIGHTING_INTENSITY = 2.5; -const float LOD_LIGHTING_INTENSITY = 1.5; -const float NON_PBR_ROUGHNESS = 0.5; const vec3 IBL_CLAMP = vec3(3.0); const float VOLUMETRIC_DENSITY_FACTOR = 0.1; const float DIELECTRIC_F0 = 0.04; @@ -448,12 +424,6 @@ float debugOutdoorFactor(float skyLight) { return baselineOutdoorFactor(skyLight); } -float classicLightCurve(float lightLevel) { - float l = clamp(lightLevel, 0.0, 1.0); - if (l <= 0.0) return 0.0; - return 0.075 + 0.925 * pow(l, 1.35); -} - vec3 computeTerrainLighting(vec3 albedo, vec3 N, vec3 V, vec3 L, float roughness, float totalShadow, float skyLight, float skyVisibility, vec3 blockLight, float ao, float ssao, out float directKeyOut, out float skyFillOut, out float blockLightOut, out float outdoorOut) { float atmosphere = skyVisibilityFactor(skyVisibility); float nDotL = max(dot(N, L), 0.0); @@ -525,62 +495,6 @@ vec4 computeVolumetric(vec3 rayStart, vec3 rayEnd, float dither) { return vec4(accumulatedScattering, transmittance); } -vec3 computeSunShafts(vec3 rayStart, vec3 rayEnd, vec3 receiverNormal, float receiverSkyLight, float receiverShadow, float dither) { - if (global.volumetric_params.x < 0.5 || global.shadow_params.w < 0.5 || global.params.w <= 0.02) return vec3(0.0); - - vec3 rayDir = rayEnd - rayStart; - float totalDist = length(rayDir); - if (totalDist <= 0.001) return vec3(0.0); - rayDir /= totalDist; - - vec3 L = normalize(global.sun_dir.xyz); - float viewScatter = pow(clamp(dot(rayDir, L) * 0.5 + 0.5, 0.0, 1.0), 2.0); - float caveGate = 1.0 - smoothstep(0.08, 0.55, clamp(receiverSkyLight, 0.0, 1.0)); - float shadowGate = smoothstep(0.06, 0.70, receiverShadow); - float visibilityGate = caveGate * mix(0.70, 1.0, shadowGate); - float receiverGate = smoothstep(-0.10, 0.55, receiverNormal.y); - visibilityGate *= receiverGate; - if (visibilityGate <= 0.001) return vec3(0.0); - - const int steps = 8; - float maxDist = min(totalDist, 80.0); - float stepSize = maxDist / float(steps); - float litAccum = 0.0; - float weightAccum = 0.0; - - for (int i = 0; i < steps; i++) { - float t = (float(i) + dither) * stepSize; - vec3 p = rayStart + rayDir * t; - float depth = length(p); - float lit = getVolShadow(p, depth); - float distanceFade = 1.0 - smoothstep(0.0, maxDist, t); - float weight = mix(0.35, 1.0, distanceFade); - litAccum += lit * weight; - weightAccum += weight; - } - - float viewBeam = litAccum / max(weightAccum, 0.001); - viewBeam *= mix(0.45, 1.0, viewScatter); - - float sunSpill = 0.0; - const int spillSteps = 6; - for (int i = 0; i < spillSteps; i++) { - float t = (float(i) + 0.5 + dither * 0.25) * 2.25; - vec3 p = rayEnd + L * t; - float lit = getVolShadow(p, length(p)); - float weight = 1.0 - float(i) / float(spillSteps); - sunSpill += lit * weight; - } - sunSpill /= 3.5; - - float beam = viewBeam * 0.65 + sunSpill * 0.18; - beam *= visibilityGate; - beam *= smoothstep(0.03, 0.40, maxDist / 80.0); - - vec3 warmSun = mix(global.sun_color.rgb, vec3(1.0, 0.84, 0.52), 0.35); - return warmSun * beam * global.volumetric_params.y * 1.35; -} - void main() { vec3 color; float outputAlpha = 1.0; @@ -632,7 +546,7 @@ void main() { vec3 L = normalize(global.sun_dir.xyz); float skyVisibility = clamp(vSkyLight, 0.0, 1.0); float atmosphericVisibility = skyVisibilityFactor(skyVisibility); - bool isCloud = vTileID < 0 && vEntranceDir.x > 0.9 && vEntranceDir.y < -0.9; + bool isCloud = vCloud > 0.5; float cascadeDistance = max(vViewDepth, 0.0); int layer = selectShadowCascade(vFragPosWorld, cascadeDistance); float shadowFactor = 0.0; @@ -729,8 +643,6 @@ void main() { color = clamp(vBlockLight, 0.0, 1.0); } else if (debugChannel < DEBUG_OUTDOOR_FACTOR + 0.5) { color = vec3(debugOutdoor); - } else if (debugChannel < DEBUG_ENTRANCE_BOUNCE + 0.5) { - color = mix(vec3(0.02, 0.02, 0.02), vec3(0.18, 0.85, 1.0), clamp(vEntranceBounce, 0.0, 1.0)); } else if (debugChannel < DEBUG_SKYLIGHT + 0.5) { color = vec3(clamp(vSkyLight, 0.0, 1.0)); } else if (debugChannel < DEBUG_AMBIENT_OCCLUSION + 0.5) { diff --git a/assets/shaders/vulkan/terrain.frag.spv b/assets/shaders/vulkan/terrain.frag.spv index 7de7e4407beddd1312447349de7433869d2b2e9e..c71a14a6539cd0dc0091aa84af8019b1c3da65c2 100644 GIT binary patch delta 14149 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 RcbfeZ{;})+eeO?J{vS(#9d!Tz diff --git a/assets/shaders/vulkan/terrain.vert b/assets/shaders/vulkan/terrain.vert index ddb8ee91..95fe35ca 100644 --- a/assets/shaders/vulkan/terrain.vert +++ b/assets/shaders/vulkan/terrain.vert @@ -6,7 +6,6 @@ layout(location = 2) in uint aNormal; layout(location = 3) in vec2 aTexCoord; layout(location = 4) in uint aPackedMeta; layout(location = 5) in uint aBlockLight; -layout(location = 6) in uint aEntranceDir; layout(location = 0) out vec3 vColor; layout(location = 1) flat out vec3 vNormal; @@ -23,9 +22,8 @@ layout(location = 11) out float vAO; layout(location = 12) out vec4 vClipPosCurrent; layout(location = 13) out vec4 vClipPosPrev; layout(location = 14) out float vMaskRadius; -layout(location = 15) out float vEntranceBounce; -layout(location = 16) out vec2 vEntranceDir; -layout(location = 17) out float vLODFade; +layout(location = 15) out float vCloud; +layout(location = 16) out float vLODFade; layout(set = 0, binding = 0) uniform GlobalUniforms { mat4 view_proj; @@ -126,12 +124,7 @@ void main() { float((aBlockLight >> 8u) & 0xFFu) / 255.0, float((aBlockLight >> 16u) & 0xFFu) / 255.0 ); - float entranceBounce = float((aBlockLight >> 24u) & 0xFFu) / 255.0; - vec2 entranceDir = vec2( - float(aEntranceDir & 0xFFu) / 255.0, - float((aEntranceDir >> 8u) & 0xFFu) / 255.0 - ) * 2.0 - 1.0; - if (dot(entranceDir, entranceDir) < 0.02) entranceDir = vec2(0.0); + float cloud = float((aBlockLight >> 24u) & 0xFFu) / 255.0; vColor = decodedColor * color_override; vNormal = decodedNormal; @@ -140,8 +133,7 @@ void main() { vDistance = length(worldPos.xyz); vSkyLight = skylight; vBlockLight = blocklight; - vEntranceBounce = entranceBounce; - vEntranceDir = entranceDir; + vCloud = cloud; vFragPosWorld = worldPos.xyz; // clipPos.w is the positive forward view distance with our reverse-Z projection. diff --git a/assets/shaders/vulkan/terrain.vert.spv b/assets/shaders/vulkan/terrain.vert.spv index 8e86a22bd030196e09bf71a07494bf8b1e243c26..ba3158423774560f48a390650475572e43f06975 100644 GIT binary patch delta 2459 zcmZ{k&u>&!6vyu{@0B*w7I2{cKr0AVv7jPWso0iw%3u{}EtM+%9Luzf9sA5od1^Fy z8;!;Ulcq~oEL>>fg3OM@xH9oSaK*;NxN>KLy3z0F&OLQP;>(=8^ZkC$z4x4R&zsK| zKbh~#Z#%HvxtuGwZuiDU-$-t7qcl3YGv{1UwnuiC?4ayn*(0*UvPWe{WlzeUk{y># z%gWckzuI*yalIE*Nj+(_G5e?IlE%B0y7br;H?MCmW}H@8sD4mN zl7_A1^mL(Cug*;SNHR&MYt8k_+EUf;9{bGF^Q#}8sjXaI4;-1MQ}twNwatAB;Z*Sd>`PDrV|-3+C%GWLy07biJ75L? zTnL!XfGr^g1H3cn3<QUU2*dQ$FeNZ~hKZ-8 zhS6!MVRTyBiD_vEe#d`c9fbebz{5(2dpM~D%gf6X)+2h1ept6kFHZ@N=sgz9;>x$h zEw;YfJ(1h9SMg~rZV+xv zTcPige^YNXZWD~gEe*)$hS0c4FdEZ&cg|(*7dhYRpmDwLktovrD*u)i49e&ETMN+c zlkJoJsV@hgO{t6f{7W41XvEkd`{i@S$J#?<;T{l%Yp>Pk3emZkO;}9u0r?z2Fk=Kp zyD05J`CsWhs~e*WL&9(v4=+COUe(H-J|qHeN=7Vu9^9PSU&v4oiTO=$SjG>`N$fl< z|Bs+CieNO>9g!c)SjSj`EyKs;cV_swFdW9iOBrT)ABzH8h|nzXzSg zbmm$4OddT3j|uB1wXLy+|8ICJY+OFvQR3;M&UX_c5N?D?oRklDQpO~f^yXm_(WYc) zWK0`A2l0`flG#D0<&)gjLGqsy2XE)h$j>18lD`R)^1LttU-*n{E>!nIh$EI54&{r7 zXM#zRgh7Hv-UvW(5~ZS#5gRM@%sd2#So z;evd)&H`T&hNrj@&EHmq7eXMx1X)|*t=dKTR7;X1L}HfEEAmO`N10W7Q5?J_^s0Qg kt%TlE@M|IvZg2VP;?M}jEFt*+ONhieucOr37`hex3p+{MRR910 delta 3069 zcmZ9N-%ngs5XUcNmr`Nd!b)3zlu{|J3aHqk_Qz6w6+{qL1W^=W%dcX$Y`f5F6HRW6 z8XFUuH0xU*jSuzB5EIiEqlwY~puYHE6XTO_CRG!o-_O}Ic@&wRf#XU@!=xt9;d zuTG^Zw>57Mf{LIfs1B}NPjyx_UGF>IbEqN+9+k98+9gj(PD#2Yy^?;(faH0}uw+z{ zm5fW?l+1nc^;fe?+R`QEO{ca-#h5ajQ z>tyFQc9*`{o}Qt^us2_rKR>dvd~RLdRj%aF((|>z(}pd6EA1-RnsA_)Th12NrV7Qp zr8Zout4^nASlBb>^2nAts?!--7mkee59Ag|##tuC<<748V#^%u4JUG|%j&E7&27F{ zU6WA1gJ95itPz`@!JyK0Y^T%9-NU9R&X*eoZ}XVj23I;<>6&JHnS%dp2{#eoM0s!I z#seZ*CfuliEiuLhyvy6^9eB6Lj1yQB>5^l#z||hpHE@l`j2yU}U~|IFA4HvZpjqG@ z9?xxVq`po>CzbM#q8K^-f>vS9*bC5<8*UW7@Ajvs+EmeD86FYNXhoW|r&*Xr5uTV} z#ud&m;|j+A@v=Vcg5dzc4VXilbp5vMU~UAC08dK6cUHoFFt=j+kCuL}Pe;#+`9}{P zx8%v7<6*cvhDn)mkK4H>!(_-c8OEP$GE97~DTX8WIfxPN(-6nZI@tO=Exfd}beaS? zfNR6vrpckVv%;NPt@q|PcaPsC;Y_f3Cw-!#<%s-igqbD$k4irl!yDo!H#-{M+2P)s zluV4n_quAvb*FT=dg&Lno$=Tu41ZNV;N8;U8l-3B!@fFU;(Y7l)JuokC!57i3&Z=^ zkqdUe2n-gyK{_14vwr^e2y-a5Yo6OD9iG`WzXzn#FXVmi_n zkbXz&=f0Mwh2ee?M=j4t=b;vCos#Dy)Pk;y{42MzlWZzk3^B2e9osBb~qN?Bh!T#>UJHO4kfU&d6s20wXyONxz}h zQhq@g4&hkLc2buzoHHY(b$-C5+~bRvtzZ+Z&Q43DbujaS$KTS&1E#lTLQ=gU*VB zw}a-T!%^i;6+s6UgyE@rNRsu%EqXtE@!`xlt>Pm$(_~3TWM)Y$OQ+g)ZL(_53By|w zE7IYt+Vj$Rs21B>lA?sz=oULKodVl+{#Nh{A~9I-Rq1f?3Jb#UR7aIo;YIIA36Rlu603Gy9K0oTSvp)ip->q9-j=^D4jaCRB?KQ&Xn#B- PG6UOn%xtC(-A()pj;bVq diff --git a/docs/lighting-architecture.md b/docs/lighting-architecture.md new file mode 100644 index 00000000..0f9e2834 --- /dev/null +++ b/docs/lighting-architecture.md @@ -0,0 +1,13 @@ +# Lighting Architecture + +Terrain uses one production lighting model: skylight and RGB block light stored in `PackedLight` and propagated by `WorldLightingEngine` across each connected loaded chunk component. Chunk arrival and block mutations trigger reconciliation; unloaded chunks are a propagation frontier. + +World generation may seed local light for initial chunk data. Runtime reconciliation is authoritative and removes stale values before meshing. Entrance-bounce storage, directional packing, and its mesh/shader path have been retired; save files contain only `PackedLight`, so no chunk migration is needed. + +Terrain vertices carry normalized skylight, RGB block light, and AO. The terrain shader combines these with CSM direct lighting, IBL sky fill, and optional volumetric lighting. Cloud vertices use the packed block-light alpha byte solely as an internal cloud marker. + +GPU meshing is disabled until its output matches the CPU vertex stride, lighting inputs, AO, and pass routing. CPU meshing is the only production terrain mesh path. + +Use `ZIGCRAFT_DEBUG_SHADER` for deterministic terrain captures. The supported capture channels are documented in `lighting-phase0-baselines.md`; capture the full scene matrix with `scripts/capture_lighting_baselines.sh`. + +Shadow quality controls PCF tap count, cascade blending, resolution, and distance. LOW, MEDIUM, HIGH, ULTRA, and EXTREME presets should be evaluated with `zig build benchmark`; the committed baseline and CI benchmark comparison are the regression gate. The CPU/shader shadow uniform block is checked by `scripts/check_shadow_abi.sh` during `zig build test`. diff --git a/docs/shaders/spirv-sizes.json b/docs/shaders/spirv-sizes.json index 0cc96d22..104e944d 100644 --- a/docs/shaders/spirv-sizes.json +++ b/docs/shaders/spirv-sizes.json @@ -27,8 +27,8 @@ "assets/shaders/vulkan/taa.frag": 9760, "assets/shaders/vulkan/taa.vert": 1160, "assets/shaders/vulkan/terrain_debug.frag": 1336, - "assets/shaders/vulkan/terrain.frag": 84752, - "assets/shaders/vulkan/terrain.vert": 11144, + "assets/shaders/vulkan/terrain.frag": 63108, + "assets/shaders/vulkan/terrain.vert": 10500, "assets/shaders/vulkan/ui.frag": 744, "assets/shaders/vulkan/ui_tex.frag": 1408, "assets/shaders/vulkan/ui_tex.vert": 1344, diff --git a/modules/engine-ecs/src/systems/render.zig b/modules/engine-ecs/src/systems/render.zig index 8135e4a2..6ae51242 100644 --- a/modules/engine-ecs/src/systems/render.zig +++ b/modules/engine-ecs/src/systems/render.zig @@ -21,7 +21,6 @@ fn makeWireframeVertex(x: f32, y: f32, z: f32) Vertex { 1.0, .{ 1.0, 1.0, 1.0 }, 1.0, - 0.0, ); } @@ -55,7 +54,7 @@ const wireframe_line_vertices = [_]Vertex{ const wireframe_line_vertex_count: u32 = @intCast(wireframe_line_vertices.len); fn makeSolidVertex(x: f32, y: f32, z: f32) Vertex { - return Vertex.init(.{ x, y, z }, .{ 1.0, 1.0, 1.0 }, .{ 0, 1, 0 }, .{ 0, 0 }, 0xffff, 1.0, .{ 1.0, 1.0, 1.0 }, 1.0, 0.0); + return Vertex.init(.{ x, y, z }, .{ 1.0, 1.0, 1.0 }, .{ 0, 1, 0 }, .{ 0, 0 }, 0xffff, 1.0, .{ 1.0, 1.0, 1.0 }, 1.0); } const solid_box_vertices = [_]Vertex{ diff --git a/modules/engine-graphics/src/cloud_system.zig b/modules/engine-graphics/src/cloud_system.zig index bfdf0370..6a4b4457 100644 --- a/modules/engine-graphics/src/cloud_system.zig +++ b/modules/engine-graphics/src/cloud_system.zig @@ -171,13 +171,12 @@ pub const CloudSystem = struct { } fn emitQuad(self: *CloudSystem, vertices: *std.ArrayListUnmanaged(rhi.Vertex), a: [3]f32, b: [3]f32, c: [3]f32, d: [3]f32, color: [3]f32, normal: [3]f32) !void { - const cloud_marker = [_]f32{ 1.0, -1.0 }; - try vertices.append(self.allocator, rhi.Vertex.initWithEntrance(a, color, normal, .{ 0.0, 1.0 }, rhi.Vertex.LOD_TILE_ID, 1.0, .{ 0.0, 0.0, 0.0 }, 1.0, 0.0, cloud_marker)); - try vertices.append(self.allocator, rhi.Vertex.initWithEntrance(b, color, normal, .{ 1.0, 1.0 }, rhi.Vertex.LOD_TILE_ID, 1.0, .{ 0.0, 0.0, 0.0 }, 1.0, 0.0, cloud_marker)); - try vertices.append(self.allocator, rhi.Vertex.initWithEntrance(c, color, normal, .{ 1.0, 0.0 }, rhi.Vertex.LOD_TILE_ID, 1.0, .{ 0.0, 0.0, 0.0 }, 1.0, 0.0, cloud_marker)); - try vertices.append(self.allocator, rhi.Vertex.initWithEntrance(c, color, normal, .{ 1.0, 0.0 }, rhi.Vertex.LOD_TILE_ID, 1.0, .{ 0.0, 0.0, 0.0 }, 1.0, 0.0, cloud_marker)); - try vertices.append(self.allocator, rhi.Vertex.initWithEntrance(d, color, normal, .{ 0.0, 0.0 }, rhi.Vertex.LOD_TILE_ID, 1.0, .{ 0.0, 0.0, 0.0 }, 1.0, 0.0, cloud_marker)); - try vertices.append(self.allocator, rhi.Vertex.initWithEntrance(a, color, normal, .{ 0.0, 1.0 }, rhi.Vertex.LOD_TILE_ID, 1.0, .{ 0.0, 0.0, 0.0 }, 1.0, 0.0, cloud_marker)); + try vertices.append(self.allocator, rhi.Vertex.initCloud(a, color, normal, .{ 0.0, 1.0 }, rhi.Vertex.LOD_TILE_ID, 1.0, .{ 0.0, 0.0, 0.0 }, 1.0)); + try vertices.append(self.allocator, rhi.Vertex.initCloud(b, color, normal, .{ 1.0, 1.0 }, rhi.Vertex.LOD_TILE_ID, 1.0, .{ 0.0, 0.0, 0.0 }, 1.0)); + try vertices.append(self.allocator, rhi.Vertex.initCloud(c, color, normal, .{ 1.0, 0.0 }, rhi.Vertex.LOD_TILE_ID, 1.0, .{ 0.0, 0.0, 0.0 }, 1.0)); + try vertices.append(self.allocator, rhi.Vertex.initCloud(c, color, normal, .{ 1.0, 0.0 }, rhi.Vertex.LOD_TILE_ID, 1.0, .{ 0.0, 0.0, 0.0 }, 1.0)); + try vertices.append(self.allocator, rhi.Vertex.initCloud(d, color, normal, .{ 0.0, 0.0 }, rhi.Vertex.LOD_TILE_ID, 1.0, .{ 0.0, 0.0, 0.0 }, 1.0)); + try vertices.append(self.allocator, rhi.Vertex.initCloud(a, color, normal, .{ 0.0, 1.0 }, rhi.Vertex.LOD_TILE_ID, 1.0, .{ 0.0, 0.0, 0.0 }, 1.0)); } fn uploadMesh(self: *CloudSystem) !void { diff --git a/modules/engine-graphics/src/shadow_system_tests.zig b/modules/engine-graphics/src/shadow_system_tests.zig index c1ce180a..afd5c1b0 100644 --- a/modules/engine-graphics/src/shadow_system_tests.zig +++ b/modules/engine-graphics/src/shadow_system_tests.zig @@ -89,7 +89,6 @@ test "ShadowConfig default field values" { try testing.expectEqual(@as(u8, 12), cfg.pcf_samples); try testing.expect(cfg.cascade_blend); try testing.expectEqual(@as(f32, 0.35), cfg.strength); - try testing.expectEqual(@as(f32, 3.0), cfg.light_size); try testing.expectEqual(@as(f32, 250.0), cfg.caster_distance); } @@ -100,7 +99,6 @@ test "ShadowConfig custom field values" { .pcf_samples = 16, .cascade_blend = false, .strength = 0.5, - .light_size = 5.0, .caster_distance = 400.0, }; @@ -109,31 +107,9 @@ test "ShadowConfig custom field values" { try testing.expectEqual(@as(u8, 16), cfg.pcf_samples); try testing.expect(!cfg.cascade_blend); try testing.expectEqual(@as(f32, 0.5), cfg.strength); - try testing.expectEqual(@as(f32, 5.0), cfg.light_size); try testing.expectEqual(@as(f32, 400.0), cfg.caster_distance); } -test "ShadowParams default light_size" { - const params = ShadowParams{ - .light_space_matrices = .{Mat4.identity} ** CASCADE_COUNT, - .cascade_splits = .{ 10.0, 50.0, 150.0, 500.0 }, - .shadow_texel_sizes = .{ 0.5, 1.0, 2.0, 4.0 }, - }; - - try testing.expectEqual(@as(f32, 3.0), params.light_size); -} - -test "ShadowParams custom light_size" { - const params = ShadowParams{ - .light_space_matrices = .{Mat4.identity} ** CASCADE_COUNT, - .cascade_splits = .{ 10.0, 50.0, 150.0, 500.0 }, - .shadow_texel_sizes = .{ 0.5, 1.0, 2.0, 4.0 }, - .light_size = 8.0, - }; - - try testing.expectEqual(@as(f32, 8.0), params.light_size); -} - test "ShadowParams all cascade splits set correctly" { const params = ShadowParams{ .light_space_matrices = .{Mat4.identity} ** CASCADE_COUNT, @@ -265,14 +241,12 @@ test "IShadowContext wrapper delegates updateUniforms" { .light_space_matrices = .{Mat4.identity} ** CASCADE_COUNT, .cascade_splits = .{ 20.0, 80.0, 250.0, 600.0 }, .shadow_texel_sizes = .{ 0.1, 0.2, 0.4, 0.8 }, - .light_size = 6.0, }; try shadow_ctx.updateUniforms(params); try testing.expectEqual(@as(u32, 1), call_count); try testing.expect(received_params != null); - try testing.expectEqual(@as(f32, 6.0), received_params.?.light_size); } test "ShadowSystemWrapper forwards all operations" { @@ -333,7 +307,6 @@ test "ShadowSystemWrapper forwards all operations" { .light_space_matrices = .{Mat4.identity} ** CASCADE_COUNT, .cascade_splits = .{ 5.0, 25.0, 100.0, 300.0 }, .shadow_texel_sizes = .{ 0.3, 0.6, 1.2, 2.4 }, - .light_size = 4.0, }; try wrapper.updateUniforms(params); try testing.expectEqual(@as(u32, 1), update_calls); diff --git a/modules/engine-graphics/src/shadow_tests.zig b/modules/engine-graphics/src/shadow_tests.zig index 7b5a6550..e7916011 100644 --- a/modules/engine-graphics/src/shadow_tests.zig +++ b/modules/engine-graphics/src/shadow_tests.zig @@ -384,7 +384,6 @@ test "ShadowConfig default values" { try testing.expectEqual(@as(u8, 9), config.pcf_samples); try testing.expect(config.cascade_blend); try testing.expectEqual(@as(f32, 0.35), config.strength); - try testing.expectEqual(@as(f32, 3.0), config.light_size); try testing.expectEqual(@as(f32, 250.0), config.caster_distance); } @@ -394,12 +393,10 @@ test "ShadowParams struct layout" { .cascade_splits = .{ 10.0, 50.0, 150.0, 500.0 }, .shadow_texel_sizes = .{ 0.5, 1.0, 2.0, 4.0 }, .shadow_depth_spans = .{ 100.0, 200.0, 400.0, 800.0 }, - .light_size = 3.0, }; try testing.expectEqual(@as(f32, 10.0), params.cascade_splits[0]); try testing.expectEqual(@as(f32, 500.0), params.cascade_splits[3]); - try testing.expectEqual(@as(f32, 3.0), params.light_size); } test "ShadowCascades isValid detects non-finite light space matrix" { @@ -459,17 +456,6 @@ test "IShadowScene renderShadowPass delegates to vtable" { try testing.expectEqual(@as(f32, 500.0), tracker.last_config.distance); } -test "ShadowParams default light_size is 3.0" { - const params = rhi.ShadowParams{ - .light_space_matrices = .{Mat4.identity} ** rhi.SHADOW_CASCADE_COUNT, - .cascade_splits = .{ 10.0, 50.0, 150.0, 500.0 }, - .shadow_texel_sizes = .{ 0.5, 1.0, 2.0, 4.0 }, - .shadow_depth_spans = .{ 100.0, 200.0, 400.0, 800.0 }, - }; - - try testing.expectEqual(@as(f32, 3.0), params.light_size); -} - test "ShadowSystem endPass is safe when pass not active" { var sys = try ShadowSystem.init(testing.allocator, 1024); defer sys.deinit(null); @@ -529,7 +515,7 @@ test "IShadowScene vtable renderShadowPass is called with correct parameters" { var custom_matrix = Mat4.identity; custom_matrix.data[0][0] = 5.0; const custom_camera = Vec3.init(100.0, -50.0, 200.0); - const custom_config = ShadowConfig{ .strength = 0.5, .light_size = 10.0 }; + const custom_config = ShadowConfig{ .strength = 0.5 }; scene.renderShadowPass(custom_matrix, custom_camera, Vec3.zero, Vec3.zero, custom_config); @@ -539,5 +525,4 @@ test "IShadowScene vtable renderShadowPass is called with correct parameters" { try testing.expectEqual(@as(f32, -50.0), verify.camera_received.y); try testing.expectEqual(@as(f32, 200.0), verify.camera_received.z); try testing.expectEqual(@as(f32, 0.5), verify.config_received.strength); - try testing.expectEqual(@as(f32, 10.0), verify.config_received.light_size); } diff --git a/modules/engine-graphics/src/vulkan/pipeline_specialized.zig b/modules/engine-graphics/src/vulkan/pipeline_specialized.zig index 8d20c389..48cd6d4d 100644 --- a/modules/engine-graphics/src/vulkan/pipeline_specialized.zig +++ b/modules/engine-graphics/src/vulkan/pipeline_specialized.zig @@ -55,20 +55,19 @@ pub fn createTerrainPipeline( const binding_description = c.VkVertexInputBindingDescription{ .binding = 0, .stride = @sizeOf(rhi.Vertex), .inputRate = c.VK_VERTEX_INPUT_RATE_VERTEX }; - var attribute_descriptions: [7]c.VkVertexInputAttributeDescription = undefined; + var attribute_descriptions: [6]c.VkVertexInputAttributeDescription = undefined; attribute_descriptions[0] = .{ .binding = 0, .location = 0, .format = c.VK_FORMAT_R32G32B32_SFLOAT, .offset = @offsetOf(rhi.Vertex, "pos") }; attribute_descriptions[1] = .{ .binding = 0, .location = 1, .format = c.VK_FORMAT_R32_UINT, .offset = @offsetOf(rhi.Vertex, "color") }; attribute_descriptions[2] = .{ .binding = 0, .location = 2, .format = c.VK_FORMAT_R32_UINT, .offset = @offsetOf(rhi.Vertex, "normal") }; attribute_descriptions[3] = .{ .binding = 0, .location = 3, .format = c.VK_FORMAT_R16G16_SFLOAT, .offset = @offsetOf(rhi.Vertex, "uv") }; attribute_descriptions[4] = .{ .binding = 0, .location = 4, .format = c.VK_FORMAT_R32_UINT, .offset = @offsetOf(rhi.Vertex, "packed_meta") }; attribute_descriptions[5] = .{ .binding = 0, .location = 5, .format = c.VK_FORMAT_R32_UINT, .offset = @offsetOf(rhi.Vertex, "blocklight") }; - attribute_descriptions[6] = .{ .binding = 0, .location = 6, .format = c.VK_FORMAT_R32_UINT, .offset = @offsetOf(rhi.Vertex, "entrance_dir") }; var vertex_input_info = std.mem.zeroes(c.VkPipelineVertexInputStateCreateInfo); vertex_input_info.sType = c.VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; vertex_input_info.vertexBindingDescriptionCount = 1; vertex_input_info.pVertexBindingDescriptions = &binding_description; - vertex_input_info.vertexAttributeDescriptionCount = 7; + vertex_input_info.vertexAttributeDescriptionCount = 6; vertex_input_info.pVertexAttributeDescriptions = &attribute_descriptions[0]; var pipeline_info = std.mem.zeroes(c.VkGraphicsPipelineCreateInfo); diff --git a/modules/engine-graphics/src/vulkan/rhi_render_state.zig b/modules/engine-graphics/src/vulkan/rhi_render_state.zig index 37671794..d259d742 100644 --- a/modules/engine-graphics/src/vulkan/rhi_render_state.zig +++ b/modules/engine-graphics/src/vulkan/rhi_render_state.zig @@ -43,7 +43,7 @@ pub fn updateGlobalUniforms(ctx: anytype, uniforms: rhi.GlobalUniforms, frame_pa .render_flags = .{ 0.0, 0.0, if (frame_params.pbr_enabled) 1.0 else 0.0, 0.0 }, .shadow_params = .{ @floatFromInt(frame_params.shadow.pcf_samples), if (frame_params.shadow.cascade_blend) 1.0 else 0.0, frame_params.shadow.strength, if (frame_params.shadow_apply_to_beauty) 1.0 else 0.0 }, .pbr_params = .{ @floatFromInt(frame_params.pbr_quality), frame_params.exposure, frame_params.saturation, if (frame_params.ssao_enabled) 1.0 else 0.0 }, - .volumetric_params = .{ if (frame_params.volumetric_enabled or frame_params.sun_shafts_enabled) 1.0 else 0.0, if (frame_params.sun_shafts_enabled) frame_params.sun_shafts_intensity else frame_params.volumetric_density, @floatFromInt(frame_params.volumetric_steps), frame_params.volumetric_scattering }, + .volumetric_params = .{ if (frame_params.volumetric_enabled) 1.0 else 0.0, frame_params.volumetric_density, @floatFromInt(frame_params.volumetric_steps), frame_params.volumetric_scattering }, .viewport_size = .{ @floatFromInt(ctx.swapchain.swapchain.extent.width), @floatFromInt(ctx.swapchain.swapchain.extent.height), if (ctx.options.debug_shadows_active) 1.0 else 0.0, @floatFromInt(ctx.options.shadow_debug_channel) }, .lpv_params = .{ if (frame_params.lpv_enabled) 1.0 else 0.0, frame_params.lpv_intensity, frame_params.lpv_cell_size, @floatFromInt(frame_params.lpv_grid_size) }, .lpv_origin = .{ frame_params.lpv_origin.x, frame_params.lpv_origin.y, frame_params.lpv_origin.z, 0.0 }, diff --git a/modules/engine-graphics/src/vulkan/rhi_shadow_bridge.zig b/modules/engine-graphics/src/vulkan/rhi_shadow_bridge.zig index ab6ee4af..e8fddb4d 100644 --- a/modules/engine-graphics/src/vulkan/rhi_shadow_bridge.zig +++ b/modules/engine-graphics/src/vulkan/rhi_shadow_bridge.zig @@ -41,7 +41,7 @@ pub fn updateShadowUniforms(ctx: anytype, params: rhi.ShadowParams) !void { .overlap_starts = overlap_starts, .shadow_texel_sizes = sizes, .shadow_depth_spans = depth_spans, - .shadow_params = .{ params.light_size, inv_resolution, 0.0, 0.0 }, + .shadow_params = .{ 0.0, inv_resolution, 0.0, 0.0 }, .fade_params = .{ params.distance * 0.9, params.distance, 0.0, 0.0 }, }; diff --git a/modules/engine-graphics/src/wireframe_cube.zig b/modules/engine-graphics/src/wireframe_cube.zig index dbbddebd..1a56a3d3 100644 --- a/modules/engine-graphics/src/wireframe_cube.zig +++ b/modules/engine-graphics/src/wireframe_cube.zig @@ -14,7 +14,6 @@ fn makeVertex(x: f32, y: f32, z: f32) Vertex { 1.0, .{ 1.0, 1.0, 1.0 }, 1.0, - 0.0, ); } diff --git a/modules/engine-rhi/src/rhi_types.zig b/modules/engine-rhi/src/rhi_types.zig index e1c80561..d3a7d38e 100644 --- a/modules/engine-rhi/src/rhi_types.zig +++ b/modules/engine-rhi/src/rhi_types.zig @@ -97,7 +97,6 @@ pub const Vertex = extern struct { uv: [2]f16, packed_meta: u32, blocklight: u32, - entrance_dir: u32, pub const LOD_TILE_ID: u16 = 0xFFFF; @@ -110,7 +109,6 @@ pub const Vertex = extern struct { skylight: f32, blocklight: [3]f32, ao: f32, - entrance_bounce: f32, ) Vertex { return .{ .pos = pos, @@ -118,12 +116,11 @@ pub const Vertex = extern struct { .normal = encodeNormal(normal), .uv = .{ @floatCast(uv[0]), @floatCast(uv[1]) }, .packed_meta = encodeMeta(tile_id, skylight, ao), - .blocklight = encodeBlocklight(blocklight, entrance_bounce), - .entrance_dir = encodeEntranceDirection(.{ 0.0, 0.0 }), + .blocklight = encodeBlocklight(blocklight, false), }; } - pub fn initWithEntrance( + pub fn initCloud( pos: [3]f32, color: [3]f32, normal: [3]f32, @@ -132,8 +129,6 @@ pub const Vertex = extern struct { skylight: f32, blocklight: [3]f32, ao: f32, - entrance_bounce: f32, - entrance_dir: [2]f32, ) Vertex { return .{ .pos = pos, @@ -141,8 +136,7 @@ pub const Vertex = extern struct { .normal = encodeNormal(normal), .uv = .{ @floatCast(uv[0]), @floatCast(uv[1]) }, .packed_meta = encodeMeta(tile_id, skylight, ao), - .blocklight = encodeBlocklight(blocklight, entrance_bounce), - .entrance_dir = encodeEntranceDirection(entrance_dir), + .blocklight = encodeBlocklight(blocklight, true), }; } @@ -158,8 +152,7 @@ pub const Vertex = extern struct { .normal = encodeNormal(normal), .uv = .{ @floatCast(uv[0]), @floatCast(uv[1]) }, .packed_meta = encodeMeta(LOD_TILE_ID, 1.0, 1.0), - .blocklight = encodeBlocklight(.{ 0.0, 0.0, 0.0 }, 0.0), - .entrance_dir = encodeEntranceDirection(.{ 0.0, 0.0 }), + .blocklight = encodeBlocklight(.{ 0.0, 0.0, 0.0 }, false), }; } }; @@ -206,22 +199,24 @@ pub fn encodeMeta(tile_id: u16, skylight: f32, ao: f32) u32 { return @as(u32, tile_id) | (@as(u32, sl) << 16) | (@as(u32, ao_u8) << 24); } -/// Encode RGB blocklight plus entrance bounce float values to RGBA8 u32. +/// Encode RGB blocklight with a cloud marker in alpha. /// Precision: 1/255 per channel. Sufficient for per-vertex lighting where values blend smoothly. -pub fn encodeBlocklight(bl: [3]f32, entrance_bounce: f32) u32 { +pub fn encodeBlocklight(bl: [3]f32, is_cloud: bool) u32 { const r: u8 = @intFromFloat(@round(@max(0.0, @min(1.0, bl[0])) * 255.0)); const g: u8 = @intFromFloat(@round(@max(0.0, @min(1.0, bl[1])) * 255.0)); const b: u8 = @intFromFloat(@round(@max(0.0, @min(1.0, bl[2])) * 255.0)); - const a: u8 = @intFromFloat(@round(@max(0.0, @min(1.0, entrance_bounce)) * 255.0)); + const a: u8 = if (is_cloud) 255 else 0; return @as(u32, r) | (@as(u32, g) << 8) | (@as(u32, b) << 16) | (@as(u32, a) << 24); } -/// Encode horizontal entrance direction to two UNORM8 channels in a u32. -/// Decoded in shaders back to [-1, 1]. Remaining bytes are reserved. -pub fn encodeEntranceDirection(dir: [2]f32) u32 { - const x: u8 = @intFromFloat(@round((@max(-1.0, @min(1.0, dir[0])) * 0.5 + 0.5) * 255.0)); - const z: u8 = @intFromFloat(@round((@max(-1.0, @min(1.0, dir[1])) * 0.5 + 0.5) * 255.0)); - return @as(u32, x) | (@as(u32, z) << 8); +test "terrain vertex ABI remains tightly packed" { + try std.testing.expectEqual(@as(usize, 32), @sizeOf(Vertex)); + try std.testing.expectEqual(@as(usize, 0), @offsetOf(Vertex, "pos")); + try std.testing.expectEqual(@as(usize, 12), @offsetOf(Vertex, "color")); + try std.testing.expectEqual(@as(usize, 16), @offsetOf(Vertex, "normal")); + try std.testing.expectEqual(@as(usize, 20), @offsetOf(Vertex, "uv")); + try std.testing.expectEqual(@as(usize, 24), @offsetOf(Vertex, "packed_meta")); + try std.testing.expectEqual(@as(usize, 28), @offsetOf(Vertex, "blocklight")); } pub const DrawMode = enum { @@ -283,7 +278,6 @@ pub const ShadowConfig = struct { pcf_samples: u8 = 9, cascade_blend: bool = true, strength: f32 = 0.35, - light_size: f32 = 3.0, caster_distance: f32 = 250.0, }; @@ -293,7 +287,6 @@ pub const ShadowParams = struct { overlap_starts: [SHADOW_CASCADE_COUNT]f32 = .{0.0} ** SHADOW_CASCADE_COUNT, shadow_texel_sizes: [SHADOW_CASCADE_COUNT]f32, shadow_depth_spans: [SHADOW_CASCADE_COUNT]f32 = .{1.0} ** SHADOW_CASCADE_COUNT, - light_size: f32 = 3.0, // PCSS light source size for penumbra estimation resolution: u32 = 4096, distance: f32 = 250.0, }; @@ -310,8 +303,6 @@ pub const FrameRenderParams = struct { shadow: ShadowConfig = .{}, pbr_quality: u8 = 0, volumetric_enabled: bool = false, - sun_shafts_enabled: bool = false, - sun_shafts_intensity: f32 = 0.0, volumetric_density: f32 = 0.05, volumetric_steps: u32 = 16, volumetric_scattering: f32 = 0.8, diff --git a/modules/engine-rhi/src/root.zig b/modules/engine-rhi/src/root.zig index 51039900..fb17de26 100644 --- a/modules/engine-rhi/src/root.zig +++ b/modules/engine-rhi/src/root.zig @@ -98,4 +98,3 @@ pub const encodeColor = rhi_types.encodeColor; pub const encodeNormal = rhi_types.encodeNormal; pub const encodeMeta = rhi_types.encodeMeta; pub const encodeBlocklight = rhi_types.encodeBlocklight; -pub const encodeEntranceDirection = rhi_types.encodeEntranceDirection; diff --git a/modules/engine-ui/src/debug_frustum.zig b/modules/engine-ui/src/debug_frustum.zig index f2eea620..10a1aa1b 100644 --- a/modules/engine-ui/src/debug_frustum.zig +++ b/modules/engine-ui/src/debug_frustum.zig @@ -12,7 +12,7 @@ pub const FRUSTUM_EDGE_COUNT: u32 = 12; pub const FRUSTUM_VERTEX_COUNT: u32 = FRUSTUM_EDGE_COUNT * 2; fn makeVertex(x: f32, y: f32, z: f32, r: f32, g: f32, b: f32) Vertex { - return Vertex.init(.{ x, y, z }, .{ r, g, b }, .{ 0, 1, 0 }, .{ 0, 0 }, 0, 1.0, .{ 1.0, 1.0, 1.0 }, 1.0, 0.0); + return Vertex.init(.{ x, y, z }, .{ r, g, b }, .{ 0, 1, 0 }, .{ 0, 0 }, 0, 1.0, .{ 1.0, 1.0, 1.0 }, 1.0); } pub const DebugFrustum = struct { diff --git a/modules/engine-ui/src/debug_menu.zig b/modules/engine-ui/src/debug_menu.zig index 958922de..f017b43a 100644 --- a/modules/engine-ui/src/debug_menu.zig +++ b/modules/engine-ui/src/debug_menu.zig @@ -23,7 +23,6 @@ pub const DebugFeature = enum(u8) { shadow_seam_diag, direct_key_debug, sky_fill_debug, - entrance_bounce_debug, block_light_debug, outdoor_factor_debug, timing_overlay, @@ -64,7 +63,6 @@ pub const FEATURE_INFOS = [DebugFeature.count]FeatureInfo{ .{ .label = "SEAM DIAG", .hotkey = "F3" }, .{ .label = "DIRECT KEY", .hotkey = "MENU" }, .{ .label = "SKY FILL", .hotkey = "MENU" }, - .{ .label = "ENTRANCE BOUNCE", .hotkey = "MENU" }, .{ .label = "BLOCK LIGHT", .hotkey = "MENU" }, .{ .label = "OUTDOOR FACTOR", .hotkey = "MENU" }, .{ .label = "TIMING OVERLAY", .hotkey = "F4" }, diff --git a/modules/game-core/src/block_outline.zig b/modules/game-core/src/block_outline.zig index 10115d3a..b8438781 100644 --- a/modules/game-core/src/block_outline.zig +++ b/modules/game-core/src/block_outline.zig @@ -17,7 +17,7 @@ const EXPAND: f32 = 0.004; /// Create a vertex with the given position fn makeVertex(x: f32, y: f32, z: f32) Vertex { - return Vertex.init(.{ x, y, z }, .{ 0.0, 0.0, 0.0 }, .{ 0, 1, 0 }, .{ 0, 0 }, 0, 1.0, .{ 1.0, 1.0, 1.0 }, 1.0, 0.0); + return Vertex.init(.{ x, y, z }, .{ 0.0, 0.0, 0.0 }, .{ 0, 1, 0 }, .{ 0, 0 }, 0, 1.0, .{ 1.0, 1.0, 1.0 }, 1.0); } /// Generate vertices for a thin quad (2 triangles = 6 vertices) diff --git a/modules/game-core/src/hand_renderer.zig b/modules/game-core/src/hand_renderer.zig index 27b63abb..562517ac 100644 --- a/modules/game-core/src/hand_renderer.zig +++ b/modules/game-core/src/hand_renderer.zig @@ -117,10 +117,10 @@ pub const HandRenderer = struct { } fn addQuad(verts: *[36]Vertex, idx: *usize, p0: [3]f32, p1: [3]f32, p2: [3]f32, p3: [3]f32, normal: [3]f32, tile: u16, color: [3]f32) void { - const v0 = Vertex.init(p0, color, normal, .{ 0, 0 }, tile, 1.0, .{ 1.0, 1.0, 1.0 }, 1.0, 0.0); - const v1 = Vertex.init(p1, color, normal, .{ 1, 0 }, tile, 1.0, .{ 1.0, 1.0, 1.0 }, 1.0, 0.0); - const v2 = Vertex.init(p2, color, normal, .{ 1, 1 }, tile, 1.0, .{ 1.0, 1.0, 1.0 }, 1.0, 0.0); - const v3 = Vertex.init(p3, color, normal, .{ 0, 1 }, tile, 1.0, .{ 1.0, 1.0, 1.0 }, 1.0, 0.0); + const v0 = Vertex.init(p0, color, normal, .{ 0, 0 }, tile, 1.0, .{ 1.0, 1.0, 1.0 }, 1.0); + const v1 = Vertex.init(p1, color, normal, .{ 1, 0 }, tile, 1.0, .{ 1.0, 1.0, 1.0 }, 1.0); + const v2 = Vertex.init(p2, color, normal, .{ 1, 1 }, tile, 1.0, .{ 1.0, 1.0, 1.0 }, 1.0); + const v3 = Vertex.init(p3, color, normal, .{ 0, 1 }, tile, 1.0, .{ 1.0, 1.0, 1.0 }, 1.0); // Triangle 1: 0 -> 2 -> 1 (CCW) verts[idx.*] = v0; diff --git a/modules/game-core/src/settings/data.zig b/modules/game-core/src/settings/data.zig index 6e1e6e1a..3b525397 100644 --- a/modules/game-core/src/settings/data.zig +++ b/modules/game-core/src/settings/data.zig @@ -13,7 +13,6 @@ pub const ShadowDebugChannel = enum(u32) { sky_fill = 8, block_light = 9, outdoor_factor = 10, - entrance_bounce = 11, skylight = 12, ambient_occlusion = 13, }; @@ -21,7 +20,6 @@ pub const ShadowDebugChannel = enum(u32) { pub fn resolveShadowDebugChannel(settings: *const @This().Settings) ShadowDebugChannel { if (settings.debug_direct_key_active) return .direct_key; if (settings.debug_sky_fill_active) return .sky_fill; - if (settings.debug_entrance_bounce_active) return .entrance_bounce; if (settings.debug_block_light_active) return .block_light; if (settings.debug_outdoor_factor_active) return .outdoor_factor; if (settings.debug_shadow_seam_diag) return .seam_diagnostics; @@ -46,7 +44,6 @@ pub fn clearTerrainDebugViews(settings: *@This().Settings) void { settings.debug_shadow_seam_diag = false; settings.debug_direct_key_active = false; settings.debug_sky_fill_active = false; - settings.debug_entrance_bounce_active = false; settings.debug_block_light_active = false; settings.debug_outdoor_factor_active = false; } @@ -105,7 +102,6 @@ pub const Settings = struct { shadow_probe_enabled: bool = false, debug_direct_key_active: bool = false, debug_sky_fill_active: bool = false, - debug_entrance_bounce_active: bool = false, debug_block_light_active: bool = false, debug_outdoor_factor_active: bool = false, debug_lpv_overlay_active: bool = false, @@ -139,8 +135,6 @@ pub const Settings = struct { // Volumetric Lighting Settings (Phase 4) volumetric_lighting_enabled: bool = false, - sun_shafts_enabled: bool = false, - sun_shafts_intensity: f32 = 0.45, volumetric_density: f32 = 0.05, // Fog density volumetric_steps: u32 = 16, // Raymarching steps volumetric_scattering: f32 = 0.8, // Mie scattering anisotropy (G) diff --git a/modules/game-core/src/settings/json_presets.zig b/modules/game-core/src/settings/json_presets.zig index a73c36f3..f0560ba7 100644 --- a/modules/game-core/src/settings/json_presets.zig +++ b/modules/game-core/src/settings/json_presets.zig @@ -27,8 +27,6 @@ pub const PresetConfig = struct { exposure: f32, saturation: f32, volumetric_lighting_enabled: bool, - sun_shafts_enabled: bool = false, - sun_shafts_intensity: f32 = 0.45, volumetric_density: f32, volumetric_steps: u32, volumetric_scattering: f32, @@ -181,8 +179,6 @@ fn applyConfig(settings: *Settings, config: PresetConfig) void { settings.exposure = config.exposure; settings.saturation = config.saturation; settings.volumetric_lighting_enabled = config.volumetric_lighting_enabled; - settings.sun_shafts_enabled = config.sun_shafts_enabled; - settings.sun_shafts_intensity = config.sun_shafts_intensity; settings.volumetric_density = config.volumetric_density; settings.volumetric_steps = config.volumetric_steps; settings.volumetric_scattering = config.volumetric_scattering; @@ -239,8 +235,6 @@ fn matches(settings: *const Settings, preset: PresetConfig) bool { settings.horizon_distance == (preset.horizon_distance orelse @import("engine-rhi").render_settings.getPresetConfig(preset.render_distance_preset).horizon_radius) and settings.render_distance_preset == preset.render_distance_preset and settings.volumetric_lighting_enabled == preset.volumetric_lighting_enabled and - settings.sun_shafts_enabled == preset.sun_shafts_enabled and - std.math.approxEqAbs(f32, settings.sun_shafts_intensity, preset.sun_shafts_intensity, epsilon) and std.math.approxEqAbs(f32, settings.volumetric_density, preset.volumetric_density, epsilon) and settings.volumetric_steps == preset.volumetric_steps and std.math.approxEqAbs(f32, settings.volumetric_scattering, preset.volumetric_scattering, epsilon) and diff --git a/modules/game-core/src/ui/session_hud.zig b/modules/game-core/src/ui/session_hud.zig index 9b01fdac..1166cd62 100644 --- a/modules/game-core/src/ui/session_hud.zig +++ b/modules/game-core/src/ui/session_hud.zig @@ -91,10 +91,10 @@ pub fn draw(session: anytype, ui: *UISystem, atlas: *const TextureAtlas, active_ const face_z = target.z + face_offset.z; const target_light = telemetry.getDebugLightInfo(target.x, target.y, target.z); const face_light = telemetry.getDebugLightInfo(face_x, face_y, face_z); - const target_light_text = if (target_light) |l| std.fmt.bufPrint(&buf2, "TARGET S:{} B:{} E:{}", .{ l.sky, l.block, l.entrance_bounce }) catch "TARGET: ???" else "TARGET: missing"; + const target_light_text = if (target_light) |l| std.fmt.bufPrint(&buf2, "TARGET S:{} B:{}", .{ l.sky, l.block }) catch "TARGET: ???" else "TARGET: missing"; Font.drawText(ui, target_light_text, ux, uy + 5, 1.5, Color.white); uy += 25; - const face_light_text = if (face_light) |l| std.fmt.bufPrint(&buf2, "FACE AIR S:{} B:{} E:{} ({}, {}, {})", .{ l.sky, l.block, l.entrance_bounce, face_x, face_y, face_z }) catch "FACE AIR: ???" else "FACE AIR: missing"; + const face_light_text = if (face_light) |l| std.fmt.bufPrint(&buf2, "FACE AIR S:{} B:{} ({}, {}, {})", .{ l.sky, l.block, face_x, face_y, face_z }) catch "FACE AIR: ???" else "FACE AIR: missing"; Font.drawText(ui, face_light_text, ux, uy + 5, 1.5, Color.white); uy += 25; const tiles_text = std.fmt.bufPrint(&buf2, "TILES: T:{} B:{} S:{}", .{ tiles.top, tiles.bottom, tiles.side }) catch "TILES: ???"; diff --git a/modules/game-ui/src/screens/world.zig b/modules/game-ui/src/screens/world.zig index 54f7c807..7af9ef56 100644 --- a/modules/game-ui/src/screens/world.zig +++ b/modules/game-ui/src/screens/world.zig @@ -791,7 +791,6 @@ pub const WorldScreen = struct { states[@intFromEnum(DebugFeature.shadow_seam_diag)] = ctx.settings.debug_shadow_seam_diag; states[@intFromEnum(DebugFeature.direct_key_debug)] = ctx.settings.debug_direct_key_active; states[@intFromEnum(DebugFeature.sky_fill_debug)] = ctx.settings.debug_sky_fill_active; - states[@intFromEnum(DebugFeature.entrance_bounce_debug)] = ctx.settings.debug_entrance_bounce_active; states[@intFromEnum(DebugFeature.block_light_debug)] = ctx.settings.debug_block_light_active; states[@intFromEnum(DebugFeature.outdoor_factor_debug)] = ctx.settings.debug_outdoor_factor_active; states[@intFromEnum(DebugFeature.timing_overlay)] = ctx.ui_manager.timing_overlay.enabled; @@ -906,13 +905,6 @@ pub const WorldScreen = struct { options.setDebugShadowView(settings_data.anyTerrainDebugActive(ctx.settings)); options.setShadowDebugChannel(resolveShadowDebugChannel(ctx.settings)); }, - .entrance_bounce_debug => { - const enable = !ctx.settings.debug_entrance_bounce_active; - settings_data.clearTerrainDebugViews(ctx.settings); - ctx.settings.debug_entrance_bounce_active = enable; - options.setDebugShadowView(settings_data.anyTerrainDebugActive(ctx.settings)); - options.setShadowDebugChannel(resolveShadowDebugChannel(ctx.settings)); - }, .block_light_debug => { const enable = !ctx.settings.debug_block_light_active; settings_data.clearTerrainDebugViews(ctx.settings); diff --git a/modules/game-ui/src/screens/world_debug.zig b/modules/game-ui/src/screens/world_debug.zig index 26e65e30..e23b6686 100644 --- a/modules/game-ui/src/screens/world_debug.zig +++ b/modules/game-ui/src/screens/world_debug.zig @@ -31,7 +31,6 @@ pub fn collectStates(screen: ScreenDebugState, ctx: WorldContext, render_system: states[@intFromEnum(DebugFeature.shadow_seam_diag)] = ctx.settings.debug_shadow_seam_diag; states[@intFromEnum(DebugFeature.direct_key_debug)] = ctx.settings.debug_direct_key_active; states[@intFromEnum(DebugFeature.sky_fill_debug)] = ctx.settings.debug_sky_fill_active; - states[@intFromEnum(DebugFeature.entrance_bounce_debug)] = ctx.settings.debug_entrance_bounce_active; states[@intFromEnum(DebugFeature.block_light_debug)] = ctx.settings.debug_block_light_active; states[@intFromEnum(DebugFeature.outdoor_factor_debug)] = ctx.settings.debug_outdoor_factor_active; states[@intFromEnum(DebugFeature.timing_overlay)] = ctx.ui_manager.timing_overlay.enabled; @@ -86,7 +85,7 @@ pub fn applyToggle(screen: ScreenDebugState, feature: DebugFeature, ctx: WorldCo if (ctx.settings.shadow_probe_enabled and !render_system.getDisableShadowDraw()) ctx.settings.shadow_sandbox_enabled = true; }, .shadow_debug, .shadow_cascade_index, .shadow_caster_coverage, .shadow_seam_diag => applyShadowDebugToggle(feature, ctx, render_system, rhi), - .direct_key_debug, .sky_fill_debug, .entrance_bounce_debug, .block_light_debug, .outdoor_factor_debug => applyTerrainDebugToggle(feature, ctx, rhi), + .direct_key_debug, .sky_fill_debug, .block_light_debug, .outdoor_factor_debug => applyTerrainDebugToggle(feature, ctx, rhi), .timing_overlay => { ctx.ui_manager.timing_overlay.toggle(); rhi.timing().setTimingEnabled(ctx.ui_manager.timing_overlay.enabled); @@ -140,7 +139,6 @@ fn applyTerrainDebugToggle(feature: DebugFeature, ctx: WorldContext, rhi: *rhi_p const enable = switch (feature) { .direct_key_debug => !ctx.settings.debug_direct_key_active, .sky_fill_debug => !ctx.settings.debug_sky_fill_active, - .entrance_bounce_debug => !ctx.settings.debug_entrance_bounce_active, .block_light_debug => !ctx.settings.debug_block_light_active, .outdoor_factor_debug => !ctx.settings.debug_outdoor_factor_active, else => unreachable, @@ -149,7 +147,6 @@ fn applyTerrainDebugToggle(feature: DebugFeature, ctx: WorldContext, rhi: *rhi_p switch (feature) { .direct_key_debug => ctx.settings.debug_direct_key_active = enable, .sky_fill_debug => ctx.settings.debug_sky_fill_active = enable, - .entrance_bounce_debug => ctx.settings.debug_entrance_bounce_active = enable, .block_light_debug => ctx.settings.debug_block_light_active = enable, .outdoor_factor_debug => ctx.settings.debug_outdoor_factor_active = enable, else => unreachable, diff --git a/modules/game-ui/src/screens/world_frame_params.zig b/modules/game-ui/src/screens/world_frame_params.zig index 1adafc24..503d5cf6 100644 --- a/modules/game-ui/src/screens/world_frame_params.zig +++ b/modules/game-ui/src/screens/world_frame_params.zig @@ -84,8 +84,6 @@ pub fn build(input: BuildInput) BuiltParams { .exposure = input.settings.exposure, .saturation = input.settings.saturation, .volumetric_enabled = input.settings.volumetric_lighting_enabled and !input.safe_mode and !input.startup_light_render, - .sun_shafts_enabled = input.settings.sun_shafts_enabled and input.shadow_sandbox_active and !input.safe_mode, - .sun_shafts_intensity = input.settings.sun_shafts_intensity, .volumetric_density = input.settings.volumetric_density, .volumetric_steps = input.settings.volumetric_steps, .volumetric_scattering = input.settings.volumetric_scattering, diff --git a/modules/world-core/src/chunk.zig b/modules/world-core/src/chunk.zig index fa7d38b8..124cbae7 100644 --- a/modules/world-core/src/chunk.zig +++ b/modules/world-core/src/chunk.zig @@ -15,10 +15,6 @@ pub const MAX_LIGHT = @import("chunk_constants.zig").MAX_LIGHT; pub const PackedLight = @import("light.zig").PackedLight; -pub const packEntranceDir = @import("light.zig").packEntranceDir; -pub const unpackEntranceDirX = @import("light.zig").unpackEntranceDirX; -pub const unpackEntranceDirZ = @import("light.zig").unpackEntranceDirZ; - pub const Chunk = struct { pub const State = enum { missing, @@ -37,8 +33,6 @@ pub const Chunk = struct { chunk_z: i32, blocks: [CHUNK_VOLUME]BlockType, light: [CHUNK_VOLUME]PackedLight, - entrance_bounce: [CHUNK_VOLUME]u4, - entrance_dir: [CHUNK_VOLUME]u8, biomes: [CHUNK_SIZE_X * CHUNK_SIZE_Z]BiomeId, heightmap: [CHUNK_SIZE_X * CHUNK_SIZE_Z]i16, state: State = .missing, @@ -62,8 +56,6 @@ pub const Chunk = struct { .chunk_z = chunk_z, .blocks = [_]BlockType{.air} ** CHUNK_VOLUME, .light = [_]PackedLight{PackedLight.init(0, 0)} ** CHUNK_VOLUME, - .entrance_bounce = [_]u4{0} ** CHUNK_VOLUME, - .entrance_dir = [_]u8{packEntranceDir(0, 0)} ** CHUNK_VOLUME, .biomes = [_]BiomeId{.plains} ** (CHUNK_SIZE_X * CHUNK_SIZE_Z), .heightmap = [_]i16{0} ** (CHUNK_SIZE_X * CHUNK_SIZE_Z), .state = .missing, @@ -154,30 +146,6 @@ pub const Chunk = struct { self.light[getIndex(x, y, z)].setSkyLight(val); } - /// Reads the entrance-bounce light value used by cave/entrance lighting propagation. - /// Coordinates must be in bounds. - pub fn getEntranceBounce(self: *const Chunk, x: u32, y: u32, z: u32) u4 { - return self.entrance_bounce[getIndex(x, y, z)]; - } - - /// Writes the entrance-bounce light value used by cave/entrance lighting propagation. - /// Coordinates must be in bounds and `val` is stored as a 4-bit value. - pub fn setEntranceBounce(self: *Chunk, x: u32, y: u32, z: u32, val: u4) void { - self.entrance_bounce[getIndex(x, y, z)] = val; - } - - /// Reads the packed horizontal direction associated with entrance lighting. - /// Coordinates must be in bounds. - pub fn getEntranceDir(self: *const Chunk, x: u32, y: u32, z: u32) u8 { - return self.entrance_dir[getIndex(x, y, z)]; - } - - /// Writes the packed horizontal direction associated with entrance lighting. - /// Use `packEntranceDir` to encode direction components. - pub fn setEntranceDir(self: *Chunk, x: u32, y: u32, z: u32, val: u8) void { - self.entrance_dir[getIndex(x, y, z)] = val; - } - /// Reads only the block-light channel at chunk-local coordinates. /// Coordinates must be in bounds. pub fn getBlockLight(self: *const Chunk, x: u32, y: u32, z: u32) u4 { @@ -224,20 +192,6 @@ pub const Chunk = struct { return self.getLight(@intCast(x), @intCast(y), @intCast(z)); } - /// Safely reads entrance-bounce light for possibly out-of-bounds local coordinates. - /// Out-of-range samples return zero bounce light. - pub fn getEntranceBounceSafe(self: *const Chunk, x: i32, y: i32, z: i32) u4 { - if (x < 0 or x >= CHUNK_SIZE_X or z < 0 or z >= CHUNK_SIZE_Z or y < 0 or y >= CHUNK_SIZE_Y) return 0; - return self.getEntranceBounce(@intCast(x), @intCast(y), @intCast(z)); - } - - /// Safely reads packed entrance direction for possibly out-of-bounds local coordinates. - /// Out-of-range samples return the neutral zero direction. - pub fn getEntranceDirSafe(self: *const Chunk, x: i32, y: i32, z: i32) u8 { - if (x < 0 or x >= CHUNK_SIZE_X or z < 0 or z >= CHUNK_SIZE_Z or y < 0 or y >= CHUNK_SIZE_Y) return packEntranceDir(0, 0); - return self.getEntranceDir(@intCast(x), @intCast(y), @intCast(z)); - } - /// Returns the world-space block X coordinate of this chunk's minimum X edge. /// Extreme chunk coordinates saturate to the nearest representable i32 edge. pub fn getWorldX(self: *const Chunk) i32 { diff --git a/modules/world-core/src/light.zig b/modules/world-core/src/light.zig index 2bb7f683..c519bc15 100644 --- a/modules/world-core/src/light.zig +++ b/modules/world-core/src/light.zig @@ -70,17 +70,3 @@ pub const PackedLight = packed struct { return @as(f32, @floatFromInt(self.getMaxLight())) / 15.0; } }; - -pub fn packEntranceDir(x: i4, z: i4) u8 { - const ux: u8 = @as(u4, @bitCast(x)); - const uz: u8 = @as(u4, @bitCast(z)); - return ux | (uz << 4); -} - -pub fn unpackEntranceDirX(encoded: u8) i4 { - return @bitCast(@as(u4, @intCast(encoded & 0x0F))); -} - -pub fn unpackEntranceDirZ(encoded: u8) i4 { - return @bitCast(@as(u4, @intCast((encoded >> 4) & 0x0F))); -} diff --git a/modules/world-core/src/light_fuzz_tests.zig b/modules/world-core/src/light_fuzz_tests.zig index 30d94ef2..ca6bb5a4 100644 --- a/modules/world-core/src/light_fuzz_tests.zig +++ b/modules/world-core/src/light_fuzz_tests.zig @@ -28,15 +28,3 @@ test "fuzz corpus: PackedLight round-trips all channel nibbles" { } } } - -test "fuzz corpus: entrance direction nibble packing preserves signed range" { - var x: i8 = -8; - while (x <= 7) : (x += 1) { - var z: i8 = -8; - while (z <= 7) : (z += 1) { - const encoded = light.packEntranceDir(@intCast(x), @intCast(z)); - try std.testing.expectEqual(@as(i4, @intCast(x)), light.unpackEntranceDirX(encoded)); - try std.testing.expectEqual(@as(i4, @intCast(z)), light.unpackEntranceDirZ(encoded)); - } - } -} diff --git a/modules/world-core/src/packed_light_tests.zig b/modules/world-core/src/packed_light_tests.zig index 95b1af44..648b0457 100644 --- a/modules/world-core/src/packed_light_tests.zig +++ b/modules/world-core/src/packed_light_tests.zig @@ -2,9 +2,6 @@ const std = @import("std"); const testing = std.testing; const PackedLight = @import("light.zig").PackedLight; const MAX_LIGHT = @import("chunk_constants.zig").MAX_LIGHT; -const packEntranceDir = @import("light.zig").packEntranceDir; -const unpackEntranceDirX = @import("light.zig").unpackEntranceDirX; -const unpackEntranceDirZ = @import("light.zig").unpackEntranceDirZ; test "PackedLight init sets sky and block light" { const light = PackedLight.init(12, 8); @@ -72,38 +69,6 @@ test "PackedLight size is 2 bytes" { try testing.expectEqual(@as(usize, 2), @sizeOf(PackedLight)); } -test "packEntranceDir encodes x and z" { - const encoded = packEntranceDir(3, -5); - try testing.expectEqual(@as(u8, 3), encoded & 0x0F); - try testing.expectEqual(@as(u8, 0xB0), encoded & 0xF0); -} - -test "packEntranceDir zero inputs" { - const encoded = packEntranceDir(0, 0); - try testing.expectEqual(@as(u8, 0), encoded); -} - -test "unpackEntranceDirX recovers original x" { - try testing.expectEqual(@as(i4, 3), unpackEntranceDirX(packEntranceDir(3, -5))); - try testing.expectEqual(@as(i4, -1), unpackEntranceDirX(packEntranceDir(-1, 7))); - try testing.expectEqual(@as(i4, 0), unpackEntranceDirX(packEntranceDir(0, 0))); -} - -test "unpackEntranceDirZ recovers original z" { - try testing.expectEqual(@as(i4, -5), unpackEntranceDirZ(packEntranceDir(3, -5))); - try testing.expectEqual(@as(i4, 7), unpackEntranceDirZ(packEntranceDir(-1, 7))); - try testing.expectEqual(@as(i4, 0), unpackEntranceDirZ(packEntranceDir(0, 0))); -} - -test "unpackEntranceDirX and unpackEntranceDirZ are inverse of packEntranceDir" { - inline for ([_]struct { i4, i4 }{ .{ 0, 0 }, .{ 1, -1 }, .{ -1, 1 }, .{ 7, 7 }, .{ -8, -8 } }) |case| { - const x, const z = case; - const encoded = packEntranceDir(x, z); - try testing.expectEqual(x, unpackEntranceDirX(encoded)); - try testing.expectEqual(z, unpackEntranceDirZ(encoded)); - } -} - test "PackedLight setBlockLight sets all RGB channels" { var light = PackedLight.init(5, 0); try testing.expectEqual(@as(u4, 0), light.block_light_r); diff --git a/modules/world-core/src/root.zig b/modules/world-core/src/root.zig index f2f0b650..0aae6f6e 100644 --- a/modules/world-core/src/root.zig +++ b/modules/world-core/src/root.zig @@ -47,9 +47,6 @@ pub const worldToChunkFromFloat = chunk.worldToChunkFromFloat; pub const worldToChunk = chunk.worldToChunk; pub const worldToLocal = chunk.worldToLocal; pub const PackedLight = light.PackedLight; -pub const packEntranceDir = light.packEntranceDir; -pub const unpackEntranceDirX = light.unpackEntranceDirX; -pub const unpackEntranceDirZ = light.unpackEntranceDirZ; pub const LODLevel = lod_data.LODLevel; pub const LODDataVersion = lod_data.LODDataVersion; pub const LODLightingHint = lod_data.LODLightingHint; diff --git a/modules/world-lod/src/lod_geometry.zig b/modules/world-lod/src/lod_geometry.zig index cbaa048a..d9222331 100644 --- a/modules/world-lod/src/lod_geometry.zig +++ b/modules/world-lod/src/lod_geometry.zig @@ -1384,7 +1384,6 @@ pub fn makeLODVertex(pos: [3]f32, col: [3]f32, norm: [3]f32, uv: [2]f32, tile_id .uv = .{ @floatCast(uv[0]), @floatCast(uv[1]) }, .packed_meta = encodeMeta(tile_id, 1.0, 1.0), .blocklight = 0, - .entrance_dir = 0, }; } diff --git a/modules/world-meshing/src/meshing/boundary.zig b/modules/world-meshing/src/meshing/boundary.zig index b0436754..6f3fe280 100644 --- a/modules/world-meshing/src/meshing/boundary.zig +++ b/modules/world-meshing/src/meshing/boundary.zig @@ -82,26 +82,6 @@ pub inline fn getLightCross(chunk: *const Chunk, neighbors: NeighborChunks, x: i return chunk.getLightSafe(x, y, z); } -pub inline fn getEntranceBounceCross(chunk: *const Chunk, neighbors: NeighborChunks, x: i32, y: i32, z: i32) u4 { - if (y < 0 or y >= CHUNK_SIZE_Y) return 0; - - if (x < 0) return if (neighbors.west) |w| w.getEntranceBounceSafe(CHUNK_SIZE_X - 1, y, z) else 0; - if (x >= CHUNK_SIZE_X) return if (neighbors.east) |e| e.getEntranceBounceSafe(0, y, z) else 0; - if (z < 0) return if (neighbors.north) |n| n.getEntranceBounceSafe(x, y, CHUNK_SIZE_Z - 1) else 0; - if (z >= CHUNK_SIZE_Z) return if (neighbors.south) |s| s.getEntranceBounceSafe(x, y, 0) else 0; - return chunk.getEntranceBounceSafe(x, y, z); -} - -pub inline fn getEntranceDirCross(chunk: *const Chunk, neighbors: NeighborChunks, x: i32, y: i32, z: i32) u8 { - if (y < 0 or y >= CHUNK_SIZE_Y) return 0; - - if (x < 0) return if (neighbors.west) |w| w.getEntranceDirSafe(CHUNK_SIZE_X - 1, y, z) else 0; - if (x >= CHUNK_SIZE_X) return if (neighbors.east) |e| e.getEntranceDirSafe(0, y, z) else 0; - if (z < 0) return if (neighbors.north) |n| n.getEntranceDirSafe(x, y, CHUNK_SIZE_Z - 1) else 0; - if (z >= CHUNK_SIZE_Z) return if (neighbors.south) |s| s.getEntranceDirSafe(x, y, 0) else 0; - return chunk.getEntranceDirSafe(x, y, z); -} - /// Get biome ID with cross-chunk neighbor lookup. /// Handles diagonal corners (both X and Z out of bounds) by clamping to the /// nearest in-bounds corner of the appropriate neighbor or current chunk. diff --git a/modules/world-meshing/src/meshing/boundary_cross_tests.zig b/modules/world-meshing/src/meshing/boundary_cross_tests.zig index 7aa11888..426770dc 100644 --- a/modules/world-meshing/src/meshing/boundary_cross_tests.zig +++ b/modules/world-meshing/src/meshing/boundary_cross_tests.zig @@ -26,31 +26,3 @@ test "getBlockCross null neighbor returns air" { try testing.expectEqual(.air, boundary.getBlockCross(&chunk, .empty, -1, 64, 8)); try testing.expectEqual(.air, boundary.getBlockCross(&chunk, .empty, 8, 64, 16)); } - -test "getEntranceBounceCross cross-chunk west neighbor returns neighbor value" { - var chunk = Chunk.init(1, 0); - var west = Chunk.init(0, 0); - west.setEntranceBounce(15, 64, 8, 5); - const neighbors = NeighborChunks{ .west = &west }; - try testing.expectEqual(@as(u4, 5), boundary.getEntranceBounceCross(&chunk, neighbors, -1, 64, 8)); -} - -test "getEntranceBounceCross out of Y range returns zero" { - var chunk = Chunk.init(0, 0); - try testing.expectEqual(@as(u4, 0), boundary.getEntranceBounceCross(&chunk, .empty, 5, -1, 10)); - try testing.expectEqual(@as(u4, 0), boundary.getEntranceBounceCross(&chunk, .empty, 5, 256, 10)); -} - -test "getEntranceDirCross cross-chunk south neighbor returns neighbor value" { - var chunk = Chunk.init(0, 0); - var south = Chunk.init(0, 1); - south.setEntranceDir(8, 64, 0, 99); - const neighbors = NeighborChunks{ .south = &south }; - try testing.expectEqual(@as(u8, 99), boundary.getEntranceDirCross(&chunk, neighbors, 8, 64, 16)); -} - -test "getEntranceDirCross null neighbor on boundary returns zero" { - var chunk = Chunk.init(0, 0); - try testing.expectEqual(@as(u8, 0), boundary.getEntranceDirCross(&chunk, .empty, 16, 64, 8)); - try testing.expectEqual(@as(u8, 0), boundary.getEntranceDirCross(&chunk, .empty, -1, 64, 8)); -} diff --git a/modules/world-meshing/src/meshing/cross_mesher.zig b/modules/world-meshing/src/meshing/cross_mesher.zig index f53a3dbb..bc898309 100644 --- a/modules/world-meshing/src/meshing/cross_mesher.zig +++ b/modules/world-meshing/src/meshing/cross_mesher.zig @@ -47,9 +47,7 @@ pub fn meshCrossBlocks( const xi: i32 = @intCast(x); const zi: i32 = @intCast(z); const light = sampleCrossLight(chunk, neighbors, xi, y, zi); - const entrance_bounce = sampleCrossEntranceBounce(chunk, neighbors, xi, y, zi); - const entrance_dir = boundary.getEntranceDirCross(chunk, neighbors, xi, y, zi); - const norm_light = lighting_sampler.normalizeLightValues(light, entrance_bounce, entrance_dir); + const norm_light = lighting_sampler.normalizeLightValues(light); const tint: [3]f32 = if (def.is_tintable) blk: { var r: f32 = 0; @@ -108,18 +106,6 @@ fn sampleCrossLight(chunk: *const Chunk, neighbors: NeighborChunks, x: i32, y: i return result; } -fn sampleCrossEntranceBounce(chunk: *const Chunk, neighbors: NeighborChunks, x: i32, y: i32, z: i32) u4 { - var result: u4 = 0; - var ox: i32 = -1; - while (ox <= 1) : (ox += 1) { - var oz: i32 = -1; - while (oz <= 1) : (oz += 1) { - result = @max(result, boundary.getEntranceBounceCross(chunk, neighbors, x + ox, y, z + oz)); - } - } - return result; -} - fn emitCrossQuad( allocator: std.mem.Allocator, verts: *std.ArrayListUnmanaged(Vertex), @@ -149,6 +135,6 @@ fn emitCrossQuad( const n_front = [6][3]f32{ nf_front, nf_front, nf_front, nf_front, nf_front, nf_front }; for (0..6) |i| { - try verts.append(allocator, Vertex.initWithEntrance(v[i], col, n_front[i], u[i], tile_id, light.skylight, light.blocklight, ao, light.entrance_bounce, light.entrance_dir)); + try verts.append(allocator, Vertex.init(v[i], col, n_front[i], u[i], tile_id, light.skylight, light.blocklight, ao)); } } diff --git a/modules/world-meshing/src/meshing/custom_mesh_mesher.zig b/modules/world-meshing/src/meshing/custom_mesh_mesher.zig index 497d259f..890a9ec6 100644 --- a/modules/world-meshing/src/meshing/custom_mesh_mesher.zig +++ b/modules/world-meshing/src/meshing/custom_mesh_mesher.zig @@ -57,9 +57,7 @@ pub fn meshCustomMeshBlocks( const xi: i32 = @intCast(x); const zi: i32 = @intCast(z); const light = sampleCustomLight(chunk, neighbors, xi, y, zi); - const entrance_bounce = sampleCustomEntranceBounce(chunk, neighbors, xi, y, zi); - const entrance_dir = boundary.getEntranceDirCross(chunk, neighbors, xi, y, zi); - const norm_light = lighting_sampler.normalizeLightValues(light, entrance_bounce, entrance_dir); + const norm_light = lighting_sampler.normalizeLightValues(light); const color = biome_color_sampler.getBlockColor(chunk, neighbors, .top, .top, y + 1, x, z, block); const xf: f32 = @floatFromInt(x); @@ -99,21 +97,6 @@ fn sampleCustomLight(chunk: *const Chunk, neighbors: NeighborChunks, x: i32, y: return result; } -fn sampleCustomEntranceBounce(chunk: *const Chunk, neighbors: NeighborChunks, x: i32, y: i32, z: i32) u4 { - var result: u4 = 0; - var oy: i32 = 0; - while (oy <= 1) : (oy += 1) { - var ox: i32 = -1; - while (ox <= 1) : (ox += 1) { - var oz: i32 = -1; - while (oz <= 1) : (oz += 1) { - result = @max(result, boundary.getEntranceBounceCross(chunk, neighbors, x + ox, y + oy, z + oz)); - } - } - } - return result; -} - fn emitBox( allocator: std.mem.Allocator, verts: *std.ArrayListUnmanaged(Vertex), @@ -155,6 +138,6 @@ fn emitQuad( const ao: f32 = 1.0; for (idx) |i| { - try verts.append(allocator, Vertex.initWithEntrance(quad.positions[i], color, quad.normal, uv[i], quad.tile_id, light.skylight, light.blocklight, ao, light.entrance_bounce, light.entrance_dir)); + try verts.append(allocator, Vertex.init(quad.positions[i], color, quad.normal, uv[i], quad.tile_id, light.skylight, light.blocklight, ao)); } } diff --git a/modules/world-meshing/src/meshing/flat_quad_mesher.zig b/modules/world-meshing/src/meshing/flat_quad_mesher.zig index 7957e2ac..f05818cb 100644 --- a/modules/world-meshing/src/meshing/flat_quad_mesher.zig +++ b/modules/world-meshing/src/meshing/flat_quad_mesher.zig @@ -47,9 +47,7 @@ pub fn meshFlatQuadBlocks( const xi: i32 = @intCast(x); const zi: i32 = @intCast(z); const light = sampleShapeLight(chunk, neighbors, xi, y, zi); - const entrance_bounce = sampleShapeEntranceBounce(chunk, neighbors, xi, y, zi); - const entrance_dir = boundary.getEntranceDirCross(chunk, neighbors, xi, y, zi); - const norm_light = lighting_sampler.normalizeLightValues(light, entrance_bounce, entrance_dir); + const norm_light = lighting_sampler.normalizeLightValues(light); const col = getShapeColor(chunk, neighbors, xi, zi, def); const tiles = atlas.getTilesForBlock(@intFromEnum(block)); @@ -81,18 +79,6 @@ fn sampleShapeLight(chunk: *const Chunk, neighbors: NeighborChunks, x: i32, y: i return result; } -fn sampleShapeEntranceBounce(chunk: *const Chunk, neighbors: NeighborChunks, x: i32, y: i32, z: i32) u4 { - var result: u4 = 0; - var ox: i32 = -1; - while (ox <= 1) : (ox += 1) { - var oz: i32 = -1; - while (oz <= 1) : (oz += 1) { - result = @max(result, boundary.getEntranceBounceCross(chunk, neighbors, x + ox, y, z + oz)); - } - } - return result; -} - fn getShapeColor(chunk: *const Chunk, neighbors: NeighborChunks, x: i32, z: i32, def: *const block_registry.BlockDefinition) [3]f32 { const tint: [3]f32 = if (def.is_tintable) blk: { var r: f32 = 0; @@ -145,6 +131,6 @@ fn emitFlatQuad( const u = [6][2]f32{ uv[0], uv[1], uv[2], uv[0], uv[2], uv[3] }; for (0..6) |i| { - try verts.append(allocator, Vertex.initWithEntrance(v[i], col, normal, u[i], tile_id, light.skylight, light.blocklight, ao, light.entrance_bounce, light.entrance_dir)); + try verts.append(allocator, Vertex.init(v[i], col, normal, u[i], tile_id, light.skylight, light.blocklight, ao)); } } diff --git a/modules/world-meshing/src/meshing/greedy_mesher.zig b/modules/world-meshing/src/meshing/greedy_mesher.zig index f0aee569..b4855137 100644 --- a/modules/world-meshing/src/meshing/greedy_mesher.zig +++ b/modules/world-meshing/src/meshing/greedy_mesher.zig @@ -40,8 +40,6 @@ pub const FaceKey = struct { block: BlockType, side: bool, light: PackedLight, - entrance_bounce: u4, - entrance_dir: u8, color: [3]f32, }; @@ -91,14 +89,10 @@ pub fn meshSlice( if (boundary.isEmittingSubchunk(axis, s - 1, u, v, y_min, y_max) and b1_emits and b1_cube and !b2_def.occludes(b1_def, axis)) { const light = lighting_sampler.sampleLightAtBoundary(chunk, neighbors, axis, s, u, v, si, true); - const entrance_bounce = lighting_sampler.sampleEntranceBounceAtBoundary(chunk, neighbors, axis, s, u, v, si, true); - const entrance_dir = lighting_sampler.sampleEntranceDirAtBoundary(chunk, neighbors, axis, s, u, v, si, true); const color = biome_color_sampler.getBlockColor(chunk, neighbors, axis, axis, s - 1, u, v, b1); - mask[u + v * du] = .{ .block = b1, .side = true, .light = light, .entrance_bounce = entrance_bounce, .entrance_dir = entrance_dir, .color = color }; + mask[u + v * du] = .{ .block = b1, .side = true, .light = light, .color = color }; } else if (boundary.isEmittingSubchunk(axis, s, u, v, y_min, y_max) and b2_emits and b2_cube and !b1_def.occludes(b2_def, axis)) { const light = lighting_sampler.sampleLightAtBoundary(chunk, neighbors, axis, s, u, v, si, false); - const entrance_bounce = lighting_sampler.sampleEntranceBounceAtBoundary(chunk, neighbors, axis, s, u, v, si, false); - const entrance_dir = lighting_sampler.sampleEntranceDirAtBoundary(chunk, neighbors, axis, s, u, v, si, false); const face = switch (axis) { .top => Face.bottom, .east => Face.west, @@ -106,7 +100,7 @@ pub fn meshSlice( else => unreachable, }; const color = biome_color_sampler.getBlockColor(chunk, neighbors, axis, face, s, u, v, b2); - mask[u + v * du] = .{ .block = b2, .side = false, .light = light, .entrance_bounce = entrance_bounce, .entrance_dir = entrance_dir, .color = color }; + mask[u + v * du] = .{ .block = b2, .side = false, .light = light, .color = color }; } } } @@ -146,7 +140,7 @@ pub fn meshSlice( .cutout => cutout_list, else => solid_list, }; - try addGreedyFace(allocator, target, axis, s, su, sv, width, height, k_def, k.side, si, k.light, k.entrance_bounce, k.entrance_dir, k.color, chunk, neighbors, atlas); + try addGreedyFace(allocator, target, axis, s, su, sv, width, height, k_def, k.side, si, k.light, k.color, chunk, neighbors, atlas); var dy: u32 = 0; while (dy < height) : (dy += 1) { @@ -175,8 +169,6 @@ fn addGreedyFace( forward: bool, si: u32, face_light: PackedLight, - face_entrance_bounce: u4, - face_entrance_dir: u8, tint: [3]f32, chunk: *const Chunk, neighbors: NeighborChunks, @@ -259,7 +251,7 @@ fn addGreedyFace( // Calculate AO for all 4 corners const ao = ao_calculator.calculateQuadAO(chunk, neighbors, axis, forward, p); - const face_light_floor = lighting_sampler.normalizeLightValues(face_light, face_entrance_bounce, face_entrance_dir); + const face_light_floor = lighting_sampler.normalizeLightValues(face_light); // Choose triangle orientation to minimize AO artifacts (flipping the diagonal) var idxs: [6]usize = undefined; @@ -271,7 +263,7 @@ fn addGreedyFace( for (idxs) |i| { const norm_light = maxLightFloor(lighting_sampler.sampleSmoothLightAtVertex(chunk, neighbors, p[i], nf), face_light_floor); - try verts.append(allocator, Vertex.initWithEntrance( + try verts.append(allocator, Vertex.init( p[i], col, nf, @@ -280,8 +272,6 @@ fn addGreedyFace( norm_light.skylight, norm_light.blocklight, ao[i], - norm_light.entrance_bounce, - norm_light.entrance_dir, )); } } @@ -292,8 +282,7 @@ fn canMergeFaces(a: FaceKey, b: FaceKey) bool { const r_diff = @as(i8, @intCast(b.light.getBlockLightR())) - @as(i8, @intCast(a.light.getBlockLightR())); const g_diff = @as(i8, @intCast(b.light.getBlockLightG())) - @as(i8, @intCast(a.light.getBlockLightG())); const bl_diff = @as(i8, @intCast(b.light.getBlockLightB())) - @as(i8, @intCast(a.light.getBlockLightB())); - const bounce_diff = @as(i8, @intCast(b.entrance_bounce)) - @as(i8, @intCast(a.entrance_bounce)); - if (@abs(sky_diff) > MAX_LIGHT_DIFF_FOR_MERGE or @abs(r_diff) > MAX_LIGHT_DIFF_FOR_MERGE or @abs(g_diff) > MAX_LIGHT_DIFF_FOR_MERGE or @abs(bl_diff) > MAX_LIGHT_DIFF_FOR_MERGE or @abs(bounce_diff) > MAX_LIGHT_DIFF_FOR_MERGE or b.entrance_dir != a.entrance_dir) return false; + if (@abs(sky_diff) > MAX_LIGHT_DIFF_FOR_MERGE or @abs(r_diff) > MAX_LIGHT_DIFF_FOR_MERGE or @abs(g_diff) > MAX_LIGHT_DIFF_FOR_MERGE or @abs(bl_diff) > MAX_LIGHT_DIFF_FOR_MERGE) return false; const diff_r = @abs(b.color[0] - a.color[0]); const diff_g = @abs(b.color[1] - a.color[1]); @@ -309,7 +298,5 @@ fn maxLightFloor(light: lighting_sampler.NormalizedLight, floor: lighting_sample @max(light.blocklight[1], floor.blocklight[1]), @max(light.blocklight[2], floor.blocklight[2]), }, - .entrance_bounce = @max(light.entrance_bounce, floor.entrance_bounce), - .entrance_dir = if (light.entrance_bounce >= floor.entrance_bounce) light.entrance_dir else floor.entrance_dir, }; } diff --git a/modules/world-meshing/src/meshing/lighting_sampler.zig b/modules/world-meshing/src/meshing/lighting_sampler.zig index 934f5a9b..d98e1317 100644 --- a/modules/world-meshing/src/meshing/lighting_sampler.zig +++ b/modules/world-meshing/src/meshing/lighting_sampler.zig @@ -7,26 +7,18 @@ const std = @import("std"); const world_core = @import("world-core"); const Chunk = world_core.Chunk; const PackedLight = world_core.PackedLight; -const unpackEntranceDirX = world_core.unpackEntranceDirX; -const unpackEntranceDirZ = world_core.unpackEntranceDirZ; const Face = world_core.Face; const block_registry = world_core.block_registry; const boundary = @import("boundary.zig"); const NeighborChunks = boundary.NeighborChunks; const SUBCHUNK_SIZE = boundary.SUBCHUNK_SIZE; -const SmoothLightSample = struct { - light: PackedLight, - entrance_bounce: u4, - entrance_dir: u8, -}; +const SmoothLightSample = struct { light: PackedLight }; /// Normalized light values ready for vertex emission. pub const NormalizedLight = struct { skylight: f32, blocklight: [3]f32, - entrance_bounce: f32, - entrance_dir: [2]f32, }; /// Sample light from the exposed air side of a face boundary. @@ -41,30 +33,8 @@ pub inline fn sampleLightAtBoundary(chunk: *const Chunk, neighbors: NeighborChun }; } -pub inline fn sampleEntranceBounceAtBoundary(chunk: *const Chunk, neighbors: NeighborChunks, axis: Face, s: i32, u: u32, v: u32, si: u32, positive_side: bool) u4 { - const y_off: i32 = @intCast(si * SUBCHUNK_SIZE); - return switch (axis) { - .top => chunk.getEntranceBounceSafe(@intCast(u), if (positive_side) s else s - 1, @intCast(v)), - .east => boundary.getEntranceBounceCross(chunk, neighbors, if (positive_side) s else s - 1, y_off + @as(i32, @intCast(u)), @intCast(v)), - .south => boundary.getEntranceBounceCross(chunk, neighbors, @intCast(u), y_off + @as(i32, @intCast(v)), if (positive_side) s else s - 1), - else => unreachable, - }; -} - -pub inline fn sampleEntranceDirAtBoundary(chunk: *const Chunk, neighbors: NeighborChunks, axis: Face, s: i32, u: u32, v: u32, si: u32, positive_side: bool) u8 { - const y_off: i32 = @intCast(si * SUBCHUNK_SIZE); - return switch (axis) { - .top => chunk.getEntranceDirSafe(@intCast(u), if (positive_side) s else s - 1, @intCast(v)), - .east => boundary.getEntranceDirCross(chunk, neighbors, if (positive_side) s else s - 1, y_off + @as(i32, @intCast(u)), @intCast(v)), - .south => boundary.getEntranceDirCross(chunk, neighbors, @intCast(u), y_off + @as(i32, @intCast(v)), if (positive_side) s else s - 1), - else => unreachable, - }; -} - /// Convert a PackedLight into normalized [0.0, 1.0] values for vertex attributes. -pub inline fn normalizeLightValues(light: PackedLight, entrance_bounce: u4, entrance_dir: u8) NormalizedLight { - const dir_x = unpackEntranceDirX(entrance_dir); - const dir_z = unpackEntranceDirZ(entrance_dir); +pub inline fn normalizeLightValues(light: PackedLight) NormalizedLight { return .{ .skylight = @as(f32, @floatFromInt(light.getSkyLight())) / 15.0, .blocklight = .{ @@ -72,17 +42,12 @@ pub inline fn normalizeLightValues(light: PackedLight, entrance_bounce: u4, entr @as(f32, @floatFromInt(light.getBlockLightG())) / 15.0, @as(f32, @floatFromInt(light.getBlockLightB())) / 15.0, }, - .entrance_bounce = @as(f32, @floatFromInt(entrance_bounce)) / 15.0, - .entrance_dir = .{ - @as(f32, @floatFromInt(dir_x)), - @as(f32, @floatFromInt(dir_z)), - }, }; } pub fn sampleSmoothLightAtVertex(chunk: *const Chunk, neighbors: NeighborChunks, pos: [3]f32, normal: [3]f32) NormalizedLight { const sample = sampleSmoothRawAtVertex(chunk, neighbors, pos, normal); - return normalizeLightValues(sample.light, sample.entrance_bounce, sample.entrance_dir); + return normalizeLightValues(sample.light); } fn sampleSmoothRawAtVertex(chunk: *const Chunk, neighbors: NeighborChunks, pos: [3]f32, normal: [3]f32) SmoothLightSample { @@ -104,8 +69,6 @@ fn sampleSmoothRawAtVertex(chunk: *const Chunk, neighbors: NeighborChunks, pos: var b_sum: u32 = 0; var count: u32 = 0; var has_direct_sun = false; - var max_bounce: u4 = 0; - var max_bounce_dir: u8 = 0; for (xs) |x| { for (ys) |y| { @@ -122,18 +85,12 @@ fn sampleSmoothRawAtVertex(chunk: *const Chunk, neighbors: NeighborChunks, pos: g_sum += light.getBlockLightG(); b_sum += light.getBlockLightB(); count += 1; - - const bounce = boundary.getEntranceBounceCross(chunk, neighbors, x, y, z); - if (bounce > max_bounce) { - max_bounce = bounce; - max_bounce_dir = boundary.getEntranceDirCross(chunk, neighbors, x, y, z); - } } } } const denom = @max(count, 1); - if (count == 0 or (sky_sum == 0 and r_sum == 0 and g_sum == 0 and b_sum == 0 and max_bounce == 0)) { + if (count == 0 or (sky_sum == 0 and r_sum == 0 and g_sum == 0 and b_sum == 0)) { return sampleExposedCell(chunk, neighbors, pos, normal); } @@ -145,8 +102,6 @@ fn sampleSmoothRawAtVertex(chunk: *const Chunk, neighbors: NeighborChunks, pos: @intCast(@min(15, (g_sum + denom / 2) / denom)), @intCast(@min(15, (b_sum + denom / 2) / denom)), ), - .entrance_bounce = max_bounce, - .entrance_dir = max_bounce_dir, }; } @@ -154,11 +109,7 @@ fn sampleExposedCell(chunk: *const Chunk, neighbors: NeighborChunks, pos: [3]f32 const cell = exposedCellFromVertex(pos, normal); const light = boundary.getLightCross(chunk, neighbors, cell[0], cell[1], cell[2]); - return .{ - .light = light, - .entrance_bounce = boundary.getEntranceBounceCross(chunk, neighbors, cell[0], cell[1], cell[2]), - .entrance_dir = boundary.getEntranceDirCross(chunk, neighbors, cell[0], cell[1], cell[2]), - }; + return .{ .light = light }; } fn exposedCellFromVertex(pos: [3]f32, normal: [3]f32) [3]i32 { @@ -199,14 +150,3 @@ test "sampleLightAtBoundary samples negative side for opposite faces" { const light = sampleLightAtBoundary(&chunk, .empty, .south, 11, 8, 9, 0, false); try std.testing.expectEqual(@as(u4, 11), light.getSkyLight()); } - -test "sampleSmoothLightAtVertex uses lit air in front of terminal tunnel wall" { - var chunk = Chunk.init(0, 0); - - chunk.setEntranceBounce(8, 4, 7, 12); - chunk.setEntranceDir(8, 4, 7, world_core.packEntranceDir(0, -1)); - - const sample = sampleSmoothLightAtVertex(&chunk, .empty, .{ 8.0, 4.0, 8.0 }, .{ 0.0, 0.0, -1.0 }); - try std.testing.expect(sample.entrance_bounce > 0.0); - try std.testing.expect(sample.entrance_dir[1] < 0.0); -} diff --git a/modules/world-meshing/src/meshing/quadric_simplifier.zig b/modules/world-meshing/src/meshing/quadric_simplifier.zig index 55ca959c..085d87cd 100644 --- a/modules/world-meshing/src/meshing/quadric_simplifier.zig +++ b/modules/world-meshing/src/meshing/quadric_simplifier.zig @@ -605,7 +605,7 @@ fn collectResults( } fn makeVertex(x: f32, y: f32, z: f32) Vertex { - return Vertex.init(.{ x, y, z }, .{ 1.0, 1.0, 1.0 }, .{ 0.0, 1.0, 0.0 }, .{ 0.0, 0.0 }, 0, 1.0, .{ 0.0, 0.0, 0.0 }, 1.0, 0.0); + return Vertex.init(.{ x, y, z }, .{ 1.0, 1.0, 1.0 }, .{ 0.0, 1.0, 0.0 }, .{ 0.0, 0.0 }, 0, 1.0, .{ 0.0, 0.0, 0.0 }, 1.0); } fn createCube(allocator: Allocator) !struct { vertices: []Vertex, indices: []u32 } { diff --git a/modules/world-meshing/src/meshing/tall_cross_mesher.zig b/modules/world-meshing/src/meshing/tall_cross_mesher.zig index 5f951488..d66932f4 100644 --- a/modules/world-meshing/src/meshing/tall_cross_mesher.zig +++ b/modules/world-meshing/src/meshing/tall_cross_mesher.zig @@ -53,9 +53,7 @@ pub fn meshTallCrossBlocks( const xi: i32 = @intCast(x); const zi: i32 = @intCast(z); const light = sampleTallCrossLight(chunk, neighbors, xi, y, zi); - const entrance_bounce = sampleTallCrossEntranceBounce(chunk, neighbors, xi, y, zi); - const entrance_dir = boundary.getEntranceDirCross(chunk, neighbors, xi, y, zi); - const norm_light = lighting_sampler.normalizeLightValues(light, entrance_bounce, entrance_dir); + const norm_light = lighting_sampler.normalizeLightValues(light); const col = getTallCrossColor(chunk, neighbors, xi, zi, def); const tiles = atlas.getTilesForBlock(@intFromEnum(block)); @@ -91,21 +89,6 @@ fn sampleTallCrossLight(chunk: *const Chunk, neighbors: NeighborChunks, x: i32, return result; } -fn sampleTallCrossEntranceBounce(chunk: *const Chunk, neighbors: NeighborChunks, x: i32, y: i32, z: i32) u4 { - var result: u4 = 0; - var oy: i32 = 0; - while (oy <= 1) : (oy += 1) { - var ox: i32 = -1; - while (ox <= 1) : (ox += 1) { - var oz: i32 = -1; - while (oz <= 1) : (oz += 1) { - result = @max(result, boundary.getEntranceBounceCross(chunk, neighbors, x + ox, y + oy, z + oz)); - } - } - } - return result; -} - fn getTallCrossColor(chunk: *const Chunk, neighbors: NeighborChunks, x: i32, z: i32, def: *const block_registry.BlockDefinition) [3]f32 { const tint: [3]f32 = if (def.is_tintable) blk: { var r: f32 = 0; @@ -161,6 +144,6 @@ fn emitTallCrossQuad( const u = [6][2]f32{ uv[0], uv[1], uv[2], uv[0], uv[2], uv[3] }; for (0..6) |i| { - try verts.append(allocator, Vertex.initWithEntrance(v[i], col, normal, u[i], tile_id, light.skylight, light.blocklight, ao, light.entrance_bounce, light.entrance_dir)); + try verts.append(allocator, Vertex.init(v[i], col, normal, u[i], tile_id, light.skylight, light.blocklight, ao)); } } diff --git a/modules/world-meshing/src/meshing/wall_attached_mesher.zig b/modules/world-meshing/src/meshing/wall_attached_mesher.zig index af20e907..d13f1833 100644 --- a/modules/world-meshing/src/meshing/wall_attached_mesher.zig +++ b/modules/world-meshing/src/meshing/wall_attached_mesher.zig @@ -56,9 +56,7 @@ pub fn meshWallAttachedBlocks( const xi: i32 = @intCast(x); const zi: i32 = @intCast(z); const light = sampleAttachedLight(chunk, neighbors, xi, y, zi); - const entrance_bounce = sampleAttachedEntranceBounce(chunk, neighbors, xi, y, zi); - const entrance_dir = boundary.getEntranceDirCross(chunk, neighbors, xi, y, zi); - const norm_light = lighting_sampler.normalizeLightValues(light, entrance_bounce, entrance_dir); + const norm_light = lighting_sampler.normalizeLightValues(light); const col = getAttachedColor(chunk, neighbors, xi, zi, def); const tiles = atlas.getTilesForBlock(@intFromEnum(block)); @@ -90,18 +88,6 @@ fn sampleAttachedLight(chunk: *const Chunk, neighbors: NeighborChunks, x: i32, y return result; } -fn sampleAttachedEntranceBounce(chunk: *const Chunk, neighbors: NeighborChunks, x: i32, y: i32, z: i32) u4 { - var result: u4 = 0; - var ox: i32 = -1; - while (ox <= 1) : (ox += 1) { - var oz: i32 = -1; - while (oz <= 1) : (oz += 1) { - result = @max(result, boundary.getEntranceBounceCross(chunk, neighbors, x + ox, y, z + oz)); - } - } - return result; -} - fn getAttachedColor(chunk: *const Chunk, neighbors: NeighborChunks, x: i32, z: i32, def: *const block_registry.BlockDefinition) [3]f32 { const tint: [3]f32 = if (def.is_tintable) blk: { var r: f32 = 0; @@ -175,6 +161,6 @@ fn emitAttachedQuad( const u = [6][2]f32{ uv[0], uv[1], uv[2], uv[0], uv[2], uv[3] }; for (0..6) |i| { - try verts.append(allocator, Vertex.initWithEntrance(v[i], col, quad.normal, u[i], tile_id, light.skylight, light.blocklight, ao, light.entrance_bounce, light.entrance_dir)); + try verts.append(allocator, Vertex.init(v[i], col, quad.normal, u[i], tile_id, light.skylight, light.blocklight, ao)); } } diff --git a/modules/world-meshing/src/root.zig b/modules/world-meshing/src/root.zig index 4cdfb454..17f256c3 100644 --- a/modules/world-meshing/src/root.zig +++ b/modules/world-meshing/src/root.zig @@ -13,8 +13,8 @@ pub const meshing = struct { pub const ao_calculator = @import("meshing/ao_calculator.zig"); pub const biome_color_sampler = @import("meshing/biome_color_sampler.zig"); pub const boundary = @import("meshing/boundary.zig"); - pub const boundary_tests = @import("meshing/boundary_tests.zig"); pub const boundary_cross_tests = @import("meshing/boundary_cross_tests.zig"); + pub const boundary_tests = @import("meshing/boundary_tests.zig"); pub const cross_mesher = @import("meshing/cross_mesher.zig"); pub const custom_mesh_mesher = @import("meshing/custom_mesh_mesher.zig"); pub const greedy_mesher = @import("meshing/greedy_mesher.zig"); diff --git a/modules/world-runtime/src/lighting_engine.zig b/modules/world-runtime/src/lighting_engine.zig index e6465f87..7fe4a6ba 100644 --- a/modules/world-runtime/src/lighting_engine.zig +++ b/modules/world-runtime/src/lighting_engine.zig @@ -102,8 +102,6 @@ const VOXEL_NEIGHBOR_OFFSETS = [_][3]i32{ .{ 1, 0, 0 }, .{ -1, 0, 0 }, .{ 0, 1, fn resetChunkLighting(chunk: *Chunk) void { for (&chunk.light) |*light| light.* = PackedLight.init(0, 0); - for (&chunk.entrance_bounce) |*bounce| bounce.* = 0; - for (&chunk.entrance_dir) |*dir| dir.* = world_core.packEntranceDir(0, 0); } fn seedChunkSunlight(chunk: *Chunk, allocator: std.mem.Allocator, queue: *std.ArrayListUnmanaged(SkyNode)) !void { @@ -225,12 +223,9 @@ test "WorldLightingEngine removes stale light across the loaded component" { center.chunk.generated = true; east.chunk.generated = true; center.chunk.setBlock(CHUNK_SIZE_X - 1, 4, 1, .torch); - east.chunk.setEntranceBounce(2, 4, 1, 7); - east.chunk.setEntranceDir(2, 4, 1, world_core.packEntranceDir(1, -1)); var lighting = WorldLightingEngine.init(&storage, testing.allocator); try lighting.afterBlockMutation(0, 0); - try testing.expectEqual(@as(u4, 0), east.chunk.getEntranceBounce(2, 4, 1)); - try testing.expectEqual(world_core.packEntranceDir(0, 0), east.chunk.getEntranceDir(2, 4, 1)); + try testing.expect(east.chunk.getLight(2, 4, 1).getBlockLightR() > 0); center.chunk.setBlock(CHUNK_SIZE_X - 1, 4, 1, .air); try lighting.afterBlockMutation(0, 0); try testing.expectEqual(@as(u4, 0), east.chunk.getLight(2, 4, 1).getBlockLightR()); diff --git a/modules/world-runtime/src/world.zig b/modules/world-runtime/src/world.zig index 5725957b..b0f59091 100644 --- a/modules/world-runtime/src/world.zig +++ b/modules/world-runtime/src/world.zig @@ -49,7 +49,6 @@ const LpvGridBuilder = @import("lpv_grid_builder.zig").LpvGridBuilder; pub const DebugLightInfo = struct { sky: u4, block: u4, - entrance_bounce: u4, }; const WorldStateData = world_core.WorldStateData; pub const GpuMeshDispatch = struct { @@ -932,7 +931,6 @@ pub const World = struct { return .{ .sky = light.getSkyLight(), .block = light.getBlockLight(), - .entrance_bounce = data.chunk.getEntranceBounce(local.x, @intCast(world_y), local.z), }; } diff --git a/modules/world-runtime/src/world_facade_tests.zig b/modules/world-runtime/src/world_facade_tests.zig index ac54bcda..2d4f7e3c 100644 --- a/modules/world-runtime/src/world_facade_tests.zig +++ b/modules/world-runtime/src/world_facade_tests.zig @@ -182,7 +182,7 @@ const MockWorld = struct { _ = world_x; _ = world_y; _ = world_z; - return .{ .sky = 15, .block = 3, .entrance_bounce = 1 }; + return .{ .sky = 15, .block = 3 }; } fn getRegionInfo(ptr: *anyopaque, world_x: i32, world_z: i32) worldgen.RegionInfo { diff --git a/modules/world-runtime/src/world_renderer.zig b/modules/world-runtime/src/world_renderer.zig index dcb31113..3b9e84fd 100644 --- a/modules/world-runtime/src/world_renderer.zig +++ b/modules/world-runtime/src/world_renderer.zig @@ -167,7 +167,8 @@ pub const WorldRenderer = struct { const vertex_allocator = try allocator.create(GlobalVertexAllocator); vertex_allocator.* = try GlobalVertexAllocator.init(allocator, rm, query, vertex_capacity_mb); - const gpu_meshing_enabled = parseEnabledEnv(getenv("ZIGCRAFT_ENABLE_GPU_MESHING"), false); + // The compute mesher does not yet match the production vertex/light contract. + const gpu_meshing_enabled = false; const max_chunks = MAX_MDI_CHUNKS; var instance_buffers: [rhi_mod.MAX_FRAMES_IN_FLIGHT]rhi_mod.BufferHandle = undefined; @@ -204,7 +205,7 @@ pub const WorldRenderer = struct { }; } } else if (!safe_mode_enabled) { - log.log.info("GPU meshing disabled by default due stale block-upload sync causing incorrect chunk texturing; set ZIGCRAFT_ENABLE_GPU_MESHING=1 to re-enable for testing", .{}); + log.log.info("GPU meshing disabled until its vertex, light, and AO output matches CPU meshing", .{}); } else { log.log.info("Safe mode: GPU meshing disabled, using CPU meshing fallback", .{}); } diff --git a/modules/worldgen-common/src/lighting_computer.zig b/modules/worldgen-common/src/lighting_computer.zig index 95f317cc..ac3c05f0 100644 --- a/modules/worldgen-common/src/lighting_computer.zig +++ b/modules/worldgen-common/src/lighting_computer.zig @@ -5,7 +5,6 @@ const CHUNK_SIZE_X = world_core.CHUNK_SIZE_X; const CHUNK_SIZE_Y = world_core.CHUNK_SIZE_Y; const CHUNK_SIZE_Z = world_core.CHUNK_SIZE_Z; const MAX_LIGHT = world_core.MAX_LIGHT; -const packEntranceDir = world_core.packEntranceDir; const block_registry = world_core.block_registry; const ILightingSystem = @import("lighting_interface.zig").ILightingSystem; @@ -28,333 +27,93 @@ pub const LightingComputer = struct { light: u4, }; - const EntranceNode = struct { - x: u8, - y: u16, - z: u8, - light: u4, - dir_x: i4, - dir_z: i4, - }; - - const EntranceSource = struct { - sky: u4, - dir_x: i4, - dir_z: i4, - }; - - const ENTRANCE_SOURCE_SKY_OFFSET: u32 = 0; - const ENTRANCE_MAX_SEED: u32 = 15; - const ENTRANCE_MIN_EDGE_CONTRAST: u4 = 1; - const ENTRANCE_PORTAL_SEED_SKY: u4 = 15; - const ENTRANCE_PORTAL_PROBE_STEPS = 3; - pub fn interface() ILightingSystem { - // LightingComputer is stateless; the interface pointer is a stable token - // used only to satisfy the shared interface shape. return .{ .ptr = &interface_token, .vtable = &INTERFACE_VTABLE }; } pub fn computeSkylight(chunk: *Chunk, allocator: std.mem.Allocator) !void { - for (&chunk.light) |*light| { - light.setSkyLight(0); - } + for (&chunk.light) |*light| light.setSkyLight(0); var queue = std.ArrayListUnmanaged(SkyNode).empty; defer queue.deinit(allocator); - var local_z: u32 = 0; - while (local_z < CHUNK_SIZE_Z) : (local_z += 1) { - var local_x: u32 = 0; - while (local_x < CHUNK_SIZE_X) : (local_x += 1) { + for (0..CHUNK_SIZE_Z) |z| { + for (0..CHUNK_SIZE_X) |x| { var sky_light: u4 = MAX_LIGHT; var y: i32 = CHUNK_SIZE_Y - 1; while (y >= 0) : (y -= 1) { const uy: u32 = @intCast(y); - const block = chunk.getBlock(local_x, uy, local_z); + const block = chunk.getBlock(@intCast(x), uy, @intCast(z)); if (block_registry.getBlockDefinition(block).isOpaque()) { - chunk.setSkyLight(local_x, uy, local_z, 0); + chunk.setSkyLight(@intCast(x), uy, @intCast(z), 0); sky_light = 0; continue; } - - chunk.setSkyLight(local_x, uy, local_z, sky_light); - if (sky_light > 0) { - try queue.append(allocator, .{ - .x = @intCast(local_x), - .y = @intCast(uy), - .z = @intCast(local_z), - .light = sky_light, - }); - } - + chunk.setSkyLight(@intCast(x), uy, @intCast(z), sky_light); + if (sky_light > 0) try queue.append(allocator, .{ .x = @intCast(x), .y = @intCast(uy), .z = @intCast(z), .light = sky_light }); const attenuation = block_registry.lightAttenuation(block); - if (attenuation > 1) { - sky_light = if (sky_light > attenuation) sky_light - attenuation else 0; - } + if (attenuation > 1) sky_light = if (sky_light > attenuation) sky_light - attenuation else 0; } } } - const propagation_neighbors = [5][3]i32{ - .{ 1, 0, 0 }, - .{ -1, 0, 0 }, - .{ 0, 0, 1 }, - .{ 0, 0, -1 }, - .{ 0, -1, 0 }, - }; - + const neighbors = [5][3]i32{ .{ 1, 0, 0 }, .{ -1, 0, 0 }, .{ 0, 0, 1 }, .{ 0, 0, -1 }, .{ 0, -1, 0 } }; var head: usize = 0; while (head < queue.items.len) : (head += 1) { const node = queue.items[head]; if (node.light <= 1) continue; - - for (propagation_neighbors) |offset| { + for (neighbors) |offset| { const nx = @as(i32, node.x) + offset[0]; const ny = @as(i32, node.y) + offset[1]; const nz = @as(i32, node.z) + offset[2]; - if (nx < 0 or nx >= CHUNK_SIZE_X or ny < 0 or ny >= CHUNK_SIZE_Y or nz < 0 or nz >= CHUNK_SIZE_Z) continue; - const ux: u32 = @intCast(nx); const uy: u32 = @intCast(ny); const uz: u32 = @intCast(nz); const block = chunk.getBlock(ux, uy, uz); if (block_registry.getBlockDefinition(block).isOpaque()) continue; - const attenuation = block_registry.lightAttenuation(block); const next_light: u4 = if (node.light > attenuation) node.light - attenuation else 0; if (next_light <= chunk.getSkyLight(ux, uy, uz)) continue; - chunk.setSkyLight(ux, uy, uz, next_light); - try queue.append(allocator, .{ - .x = @intCast(ux), - .y = @intCast(uy), - .z = @intCast(uz), - .light = next_light, - }); + try queue.append(allocator, .{ .x = @intCast(ux), .y = @intCast(uy), .z = @intCast(uz), .light = next_light }); } } - - try computeEntranceBounce(chunk, allocator); } - fn computeEntranceBounce(chunk: *Chunk, allocator: std.mem.Allocator) !void { - for (&chunk.entrance_bounce) |*bounce| { - bounce.* = 0; - } - for (&chunk.entrance_dir) |*dir| { - dir.* = packEntranceDir(0, 0); - } + pub fn computeBlockLight(chunk: *Chunk, allocator: std.mem.Allocator) !void { + for (&chunk.light) |*light| light.setBlockLightRGB(0, 0, 0); - var queue = std.ArrayListUnmanaged(EntranceNode).empty; + var queue = std.ArrayListUnmanaged(LightNode).empty; defer queue.deinit(allocator); - - var local_z: u32 = 0; - while (local_z < CHUNK_SIZE_Z) : (local_z += 1) { - var local_x: u32 = 0; - while (local_x < CHUNK_SIZE_X) : (local_x += 1) { - var covered = false; - var y: i32 = CHUNK_SIZE_Y - 1; - while (y >= 0) : (y -= 1) { - const uy: u32 = @intCast(y); - const block = chunk.getBlock(local_x, uy, local_z); - if (block_registry.getBlockDefinition(block).isOpaque()) { - covered = true; - continue; - } - - if (!covered and !touchesOpaqueNeighbor(chunk, local_x, uy, local_z)) continue; - - const sky = chunk.getSkyLight(local_x, uy, local_z); - if (sky >= MAX_LIGHT) continue; - const source = entranceSourceSky(chunk, local_x, uy, local_z, sky) orelse continue; - - const seed: u4 = @intCast(@min(ENTRANCE_MAX_SEED, @max(@as(u32, sky), @as(u32, source.sky)) - ENTRANCE_SOURCE_SKY_OFFSET)); - chunk.setEntranceBounce(local_x, uy, local_z, seed); - chunk.setEntranceDir(local_x, uy, local_z, packEntranceDir(source.dir_x, source.dir_z)); - try queue.append(allocator, .{ .x = @intCast(local_x), .y = @intCast(uy), .z = @intCast(local_z), .light = seed, .dir_x = source.dir_x, .dir_z = source.dir_z }); - } - } - } + for (0..CHUNK_SIZE_Z) |z| for (0..CHUNK_SIZE_Y) |y| for (0..CHUNK_SIZE_X) |x| { + const block = chunk.getBlock(@intCast(x), @intCast(y), @intCast(z)); + const emission = block_registry.getBlockDefinition(block).light_emission; + if (emission[0] == 0 and emission[1] == 0 and emission[2] == 0) continue; + chunk.setBlockLightRGB(@intCast(x), @intCast(y), @intCast(z), emission[0], emission[1], emission[2]); + try queue.append(allocator, .{ .x = @intCast(x), .y = @intCast(y), .z = @intCast(z), .r = emission[0], .g = emission[1], .b = emission[2] }); + }; const neighbors = [6][3]i32{ .{ 1, 0, 0 }, .{ -1, 0, 0 }, .{ 0, 1, 0 }, .{ 0, -1, 0 }, .{ 0, 0, 1 }, .{ 0, 0, -1 } }; var head: usize = 0; while (head < queue.items.len) : (head += 1) { const node = queue.items[head]; - if (node.light <= 1) continue; - for (neighbors) |offset| { const nx = @as(i32, node.x) + offset[0]; const ny = @as(i32, node.y) + offset[1]; const nz = @as(i32, node.z) + offset[2]; if (nx < 0 or nx >= CHUNK_SIZE_X or ny < 0 or ny >= CHUNK_SIZE_Y or nz < 0 or nz >= CHUNK_SIZE_Z) continue; - const ux: u32 = @intCast(nx); const uy: u32 = @intCast(ny); const uz: u32 = @intCast(nz); - const block = chunk.getBlock(ux, uy, uz); - if (block_registry.getBlockDefinition(block).isOpaque()) continue; - - const offset_x = offset[0]; - const offset_z = offset[2]; - const away_from_aperture = offset[1] == 0 and offset_x == -@as(i32, node.dir_x) and offset_z == -@as(i32, node.dir_z); - const toward_aperture = offset[1] == 0 and offset_x == @as(i32, node.dir_x) and offset_z == @as(i32, node.dir_z); - const horizontal_turn = offset[1] == 0 and !away_from_aperture and !toward_aperture; - const attenuation: u4 = if (away_from_aperture) - 1 - else if (horizontal_turn) - 2 - else if (toward_aperture) - 4 - else - 3; - if (node.light <= attenuation) continue; - - const next_light: u4 = node.light - attenuation; - if (next_light == 0) continue; - if (next_light <= chunk.getEntranceBounce(ux, uy, uz)) continue; - - chunk.setEntranceBounce(ux, uy, uz, next_light); - chunk.setEntranceDir(ux, uy, uz, packEntranceDir(node.dir_x, node.dir_z)); - try queue.append(allocator, .{ .x = @intCast(ux), .y = @intCast(uy), .z = @intCast(uz), .light = next_light, .dir_x = node.dir_x, .dir_z = node.dir_z }); - } - } - } - - fn touchesOpaqueNeighbor(chunk: *const Chunk, x: u32, y: u32, z: u32) bool { - const neighbors = [6][3]i32{ .{ 1, 0, 0 }, .{ -1, 0, 0 }, .{ 0, 1, 0 }, .{ 0, -1, 0 }, .{ 0, 0, 1 }, .{ 0, 0, -1 } }; - for (neighbors) |offset| { - const nx = @as(i32, @intCast(x)) + offset[0]; - const ny = @as(i32, @intCast(y)) + offset[1]; - const nz = @as(i32, @intCast(z)) + offset[2]; - if (nx < 0 or nx >= CHUNK_SIZE_X or ny < 0 or ny >= CHUNK_SIZE_Y or nz < 0 or nz >= CHUNK_SIZE_Z) continue; - - const block = chunk.getBlock(@intCast(nx), @intCast(ny), @intCast(nz)); - if (block_registry.getBlockDefinition(block).isOpaque()) return true; - } - return false; - } - - fn entranceSourceSky(chunk: *const Chunk, x: u32, y: u32, z: u32, sky: u4) ?EntranceSource { - const neighbors = [4][3]i32{ .{ 1, 0, 0 }, .{ -1, 0, 0 }, .{ 0, 0, 1 }, .{ 0, 0, -1 } }; - var source_sky: u4 = 0; - var source_dir_x: i4 = 0; - var source_dir_z: i4 = 0; - for (neighbors) |offset| { - var step: i32 = 1; - while (step <= ENTRANCE_PORTAL_PROBE_STEPS) : (step += 1) { - const nx = @as(i32, @intCast(x)) + offset[0] * step; - const ny = @as(i32, @intCast(y)); - const nz = @as(i32, @intCast(z)) + offset[2] * step; - if (nx < 0 or nx >= CHUNK_SIZE_X or nz < 0 or nz >= CHUNK_SIZE_Z) { - if (sky >= 6) { - const candidate_sky = @max(sky, ENTRANCE_PORTAL_SEED_SKY); - if (candidate_sky > source_sky) { - source_sky = candidate_sky; - source_dir_x = @intCast(offset[0]); - source_dir_z = @intCast(offset[2]); - } - } - break; - } - if (ny < 0 or ny >= CHUNK_SIZE_Y) break; - - const neighbor_block = chunk.getBlock(@intCast(nx), @intCast(ny), @intCast(nz)); - if (block_registry.getBlockDefinition(neighbor_block).isOpaque()) break; - - const neighbor_sky = chunk.getSkyLight(@intCast(nx), @intCast(ny), @intCast(nz)); - if (neighbor_sky >= sky + ENTRANCE_MIN_EDGE_CONTRAST or !isCoveredByColumn(chunk, @intCast(nx), @intCast(ny), @intCast(nz))) { - const candidate_sky = @max(neighbor_sky, ENTRANCE_PORTAL_SEED_SKY); - if (candidate_sky > source_sky) { - source_sky = candidate_sky; - source_dir_x = @intCast(offset[0]); - source_dir_z = @intCast(offset[2]); - } - break; - } - } - } - return if (source_sky > sky) .{ .sky = source_sky, .dir_x = source_dir_x, .dir_z = source_dir_z } else null; - } - - fn isCoveredByColumn(chunk: *const Chunk, x: u32, y: u32, z: u32) bool { - var yy = y + 1; - while (yy < CHUNK_SIZE_Y) : (yy += 1) { - const block = chunk.getBlock(x, yy, z); - if (block_registry.getBlockDefinition(block).isOpaque()) return true; - } - return false; - } - - pub fn computeBlockLight(chunk: *Chunk, allocator: std.mem.Allocator) !void { - for (&chunk.light) |*light| { - light.setBlockLightRGB(0, 0, 0); - } - - var queue = std.ArrayListUnmanaged(LightNode).empty; - defer queue.deinit(allocator); - var local_z: u32 = 0; - while (local_z < CHUNK_SIZE_Z) : (local_z += 1) { - var y: u32 = 0; - while (y < CHUNK_SIZE_Y) : (y += 1) { - var local_x: u32 = 0; - while (local_x < CHUNK_SIZE_X) : (local_x += 1) { - const block = chunk.getBlock(local_x, y, local_z); - const emission = block_registry.getBlockDefinition(block).light_emission; - if (emission[0] > 0 or emission[1] > 0 or emission[2] > 0) { - chunk.setBlockLightRGB(local_x, y, local_z, emission[0], emission[1], emission[2]); - try queue.append(allocator, .{ - .x = @intCast(local_x), - .y = @intCast(y), - .z = @intCast(local_z), - .r = emission[0], - .g = emission[1], - .b = emission[2], - }); - } - } - } - } - var head: usize = 0; - while (head < queue.items.len) : (head += 1) { - const node = queue.items[head]; - const neighbors = [6][3]i32{ .{ 1, 0, 0 }, .{ -1, 0, 0 }, .{ 0, 1, 0 }, .{ 0, -1, 0 }, .{ 0, 0, 1 }, .{ 0, 0, -1 } }; - for (neighbors) |offset| { - const nx = @as(i32, node.x) + offset[0]; - const ny = @as(i32, node.y) + offset[1]; - const nz = @as(i32, node.z) + offset[2]; - if (nx >= 0 and nx < CHUNK_SIZE_X and ny >= 0 and ny < CHUNK_SIZE_Y and nz >= 0 and nz < CHUNK_SIZE_Z) { - const ux: u32 = @intCast(nx); - const uy: u32 = @intCast(ny); - const uz: u32 = @intCast(nz); - const block = chunk.getBlock(ux, uy, uz); - if (!block_registry.getBlockDefinition(block).isOpaque()) { - const current_light = chunk.getLight(ux, uy, uz); - const current_r = current_light.getBlockLightR(); - const current_g = current_light.getBlockLightG(); - const current_b = current_light.getBlockLightB(); - - const next_r: u4 = if (node.r > 1) node.r - 1 else 0; - const next_g: u4 = if (node.g > 1) node.g - 1 else 0; - const next_b: u4 = if (node.b > 1) node.b - 1 else 0; - - if (next_r > current_r or next_g > current_g or next_b > current_b) { - const new_r = @max(next_r, current_r); - const new_g = @max(next_g, current_g); - const new_b = @max(next_b, current_b); - chunk.setBlockLightRGB(ux, uy, uz, new_r, new_g, new_b); - try queue.append(allocator, .{ - .x = @intCast(nx), - .y = @intCast(ny), - .z = @intCast(nz), - .r = new_r, - .g = new_g, - .b = new_b, - }); - } - } - } + if (block_registry.getBlockDefinition(chunk.getBlock(ux, uy, uz)).isOpaque()) continue; + const current = chunk.getLight(ux, uy, uz); + const r: u4 = @max(if (node.r > 1) node.r - 1 else 0, current.getBlockLightR()); + const g: u4 = @max(if (node.g > 1) node.g - 1 else 0, current.getBlockLightG()); + const b: u4 = @max(if (node.b > 1) node.b - 1 else 0, current.getBlockLightB()); + if (r == current.getBlockLightR() and g == current.getBlockLightG() and b == current.getBlockLightB()) continue; + chunk.setBlockLightRGB(ux, uy, uz, r, g, b); + try queue.append(allocator, .{ .x = @intCast(ux), .y = @intCast(uy), .z = @intCast(uz), .r = r, .g = g, .b = b }); } } } @@ -378,90 +137,15 @@ fn interfaceComputeBlockLight(ptr: *anyopaque, chunk: *Chunk, allocator: std.mem test "computeSkylight preserves full light in open columns" { var chunk = Chunk.init(0, 0); try LightingComputer.computeSkylight(&chunk, std.testing.allocator); - try std.testing.expectEqual(@as(u4, MAX_LIGHT), chunk.getSkyLight(8, 255, 8)); try std.testing.expectEqual(@as(u4, MAX_LIGHT), chunk.getSkyLight(8, 64, 8)); } test "computeSkylight propagates sideways from an open shaft" { var chunk = Chunk.init(0, 0); - - for (0..CHUNK_SIZE_Z) |z| { - for (0..CHUNK_SIZE_X) |x| { - chunk.setBlock(@intCast(x), 4, @intCast(z), .stone); - } - } + for (0..CHUNK_SIZE_Z) |z| for (0..CHUNK_SIZE_X) |x| chunk.setBlock(@intCast(x), 4, @intCast(z), .stone); chunk.setBlock(8, 4, 8, .air); - try LightingComputer.computeSkylight(&chunk, std.testing.allocator); - try std.testing.expectEqual(@as(u4, MAX_LIGHT), chunk.getSkyLight(8, 3, 8)); try std.testing.expect(chunk.getSkyLight(9, 3, 8) > 0); } - -test "entrance bounce carries around tunnel corners" { - var chunk = Chunk.init(0, 0); - - for (0..CHUNK_SIZE_Z) |z| { - for (0..CHUNK_SIZE_X) |x| { - chunk.setBlock(@intCast(x), 4, @intCast(z), .stone); - } - } - - // Open shaft to the sky and a one-block-high tunnel that turns a corner. - chunk.setBlock(4, 4, 4, .air); - for (5..11) |x| { - chunk.setBlock(@intCast(x), 3, 4, .air); - } - for (5..10) |z| { - chunk.setBlock(10, 3, @intCast(z), .air); - } - - try LightingComputer.computeSkylight(&chunk, std.testing.allocator); - - try std.testing.expect(chunk.getEntranceBounce(8, 3, 4) >= chunk.getEntranceBounce(10, 3, 8)); - try std.testing.expect(chunk.getEntranceBounce(10, 3, 8) > 0); -} - -test "entrance bounce seeds side openings without vertical cover" { - var chunk = Chunk.init(0, 0); - - // Build a vertical wall with a one-block recess at y=4. The recess column - // itself has open sky above, so old covered-column-only seeding missed it. - var y: u32 = 0; - while (y <= 8) : (y += 1) { - var z: u32 = 6; - while (z <= 10) : (z += 1) { - chunk.setBlock(8, y, z, .stone); - } - } - chunk.setBlock(8, 4, 8, .air); - - try LightingComputer.computeSkylight(&chunk, std.testing.allocator); - - try std.testing.expect(chunk.getEntranceBounce(8, 4, 8) > 0); -} - -test "entrance bounce fills a three-deep side tunnel" { - var chunk = Chunk.init(0, 0); - - var z: u32 = 4; - while (z <= 9) : (z += 1) { - var y: u32 = 3; - while (y <= 5) : (y += 1) { - var x: u32 = 7; - while (x <= 9) : (x += 1) { - chunk.setBlock(x, y, z, .stone); - } - } - } - - z = 4; - while (z <= 8) : (z += 1) { - chunk.setBlock(8, 4, z, .air); - } - - try LightingComputer.computeSkylight(&chunk, std.testing.allocator); - - try std.testing.expect(chunk.getEntranceBounce(8, 4, 8) > 0); -} diff --git a/src/game/app.zig b/src/game/app.zig index 4a7184c4..4d5feae7 100644 --- a/src/game/app.zig +++ b/src/game/app.zig @@ -610,7 +610,6 @@ fn applyShadowTestPreset(settings: *Settings) void { settings.shadow_cascade_blend = false; settings.pbr_enabled = false; settings.volumetric_lighting_enabled = false; - settings.sun_shafts_enabled = false; settings.ssao_enabled = false; settings.lpv_enabled = false; settings.taa_enabled = false; diff --git a/src/world_inline_tests.zig b/src/world_inline_tests.zig index dccbee05..ff157711 100644 --- a/src/world_inline_tests.zig +++ b/src/world_inline_tests.zig @@ -4,7 +4,6 @@ const Vec3 = @import("zig-math").Vec3; const world_core = @import("world-core"); const Chunk = world_core.Chunk; const PackedLight = world_core.PackedLight; -const packEntranceDir = world_core.packEntranceDir; const CHUNK_SIZE_X = world_core.CHUNK_SIZE_X; const CHUNK_SIZE_Y = world_core.CHUNK_SIZE_Y; const CHUNK_SIZE_Z = world_core.CHUNK_SIZE_Z; @@ -386,35 +385,27 @@ test "calculateVertexAO corner only occlusion" { test "normalizeLightValues zero light" { const light = PackedLight.init(0, 0); - const norm = lighting_sampler.normalizeLightValues(light, 0, packEntranceDir(0, 0)); + const norm = lighting_sampler.normalizeLightValues(light); try testing.expectApproxEqAbs(@as(f32, 0.0), norm.skylight, 0.001); try testing.expectApproxEqAbs(@as(f32, 0.0), norm.blocklight[0], 0.001); try testing.expectApproxEqAbs(@as(f32, 0.0), norm.blocklight[1], 0.001); try testing.expectApproxEqAbs(@as(f32, 0.0), norm.blocklight[2], 0.001); - try testing.expectApproxEqAbs(@as(f32, 0.0), norm.entrance_dir[0], 0.001); - try testing.expectApproxEqAbs(@as(f32, 0.0), norm.entrance_dir[1], 0.001); } test "normalizeLightValues max light" { const light = PackedLight.init(15, 15); - const norm = lighting_sampler.normalizeLightValues(light, 15, packEntranceDir(1, 0)); + const norm = lighting_sampler.normalizeLightValues(light); try testing.expectApproxEqAbs(@as(f32, 1.0), norm.skylight, 0.001); try testing.expectApproxEqAbs(@as(f32, 1.0), norm.blocklight[0], 0.001); - try testing.expectApproxEqAbs(@as(f32, 1.0), norm.entrance_bounce, 0.001); - try testing.expectApproxEqAbs(@as(f32, 1.0), norm.entrance_dir[0], 0.001); - try testing.expectApproxEqAbs(@as(f32, 0.0), norm.entrance_dir[1], 0.001); } test "normalizeLightValues RGB channels" { const light = PackedLight.initRGB(8, 4, 8, 12); - const norm = lighting_sampler.normalizeLightValues(light, 6, packEntranceDir(-1, 1)); + const norm = lighting_sampler.normalizeLightValues(light); try testing.expectApproxEqAbs(@as(f32, 8.0 / 15.0), norm.skylight, 0.001); try testing.expectApproxEqAbs(@as(f32, 4.0 / 15.0), norm.blocklight[0], 0.001); try testing.expectApproxEqAbs(@as(f32, 8.0 / 15.0), norm.blocklight[1], 0.001); try testing.expectApproxEqAbs(@as(f32, 12.0 / 15.0), norm.blocklight[2], 0.001); - try testing.expectApproxEqAbs(@as(f32, 6.0 / 15.0), norm.entrance_bounce, 0.001); - try testing.expectApproxEqAbs(@as(f32, -1.0), norm.entrance_dir[0], 0.001); - try testing.expectApproxEqAbs(@as(f32, 1.0), norm.entrance_dir[1], 0.001); } test "getBlockColor returns no tint for stone" {