@@ -93,6 +93,7 @@ static bool pl__terrain_load(plTerrain* ptTerrain, plTerrainProcessInfo* ptInfo)
9393void pl__remove_from_replacement_queue (plTerrain * ptTerrain , plTerrainChunk * ptChunk );
9494
9595static void pl__render_chunk (plScene * , plTerrain * , plCamera * , plRenderEncoder * , plTerrainChunk * , plTerrainChunkFile * , const plMat4 * ptMVP , uint32_t );
96+ static void pl__render_chunk_shadow (plScene * , plTerrain * , plCamera * , plRenderEncoder * , plTerrainChunk * , plTerrainChunkFile * , const plMat4 * ptMVP , uint32_t );
9697
9798static void pl__free_chunk_until (plTerrain * P , uint64_t idx_bytes_needed , uint64_t vtx_bytes_needed );
9899
@@ -995,6 +996,83 @@ pl__render_chunk(plScene* ptScene, plTerrain* ptTerrain, plCamera* ptCamera , pl
995996 }
996997}
997998
999+ static void
1000+ pl__render_chunk_shadow (plScene * ptScene , plTerrain * ptTerrain , plCamera * ptCamera , plRenderEncoder * ptEncoder , plTerrainChunk * ptChunk , plTerrainChunkFile * ptFile , const plMat4 * ptMVP , uint32_t uGlobalIndex )
1001+ {
1002+ PL_ASSERT (ptChunk != NULL );
1003+
1004+ plAABB tAABB = {
1005+ .tMin = ptChunk -> tMinBound ,
1006+ .tMax = ptChunk -> tMaxBound
1007+ };
1008+
1009+ if (!pl__renderer_sat_visibility_test (ptCamera , & tAABB ))
1010+ return ;
1011+
1012+ plVec3 tClosestPoint = gptCollision -> point_closest_point_aabb (ptCamera -> tPos , tAABB );
1013+ float fDistance = fabsf (pl_length_vec3 (pl_sub_vec3 (tClosestPoint , ptCamera -> tPos )));
1014+
1015+ pl__request_residency (ptTerrain , ptChunk );
1016+
1017+ if (ptChunk -> ptIndexHole == NULL )
1018+ return ;
1019+
1020+ float fViewportWidth = gptIOI -> get_io ()-> tMainViewportSize .x ;
1021+ float fHorizontalFieldOfView = 2.0f * atanf (tanf (0.5f * ptCamera -> fFieldOfView ) * ptCamera -> fAspectRatio );
1022+
1023+ float fK = fViewportWidth / (2.0f * tanf (0.5f * fHorizontalFieldOfView ));
1024+
1025+ float fGeometricError = ptFile -> fMaxBaseError * (float )ptChunk -> uLevel ;
1026+ float fRho = fGeometricError * fK / fDistance ;
1027+
1028+
1029+ float tauSubdivide = ptTerrain -> tRuntimeOptions .fTau ;
1030+ float tauMerge = tauSubdivide * 0.5f ;
1031+
1032+ bool bChildrenResident = pl__all_children_resident (ptChunk );
1033+
1034+ // Decide refinement using hysteresis
1035+ if (!bChildrenResident || fRho <= tauSubdivide )
1036+ {
1037+ const plDrawIndex tDraw = {
1038+ .uInstanceCount = 1 ,
1039+ .uIndexCount = ptChunk -> uIndexCount ,
1040+ .uVertexStart = (uint32_t )(ptChunk -> ptVertexHole -> uOffset / sizeof (plTerrainVertex )),
1041+ .uIndexStart = (uint32_t )(ptChunk -> ptIndexHole -> uOffset / sizeof (uint32_t )),
1042+ .tIndexBuffer = ptTerrain -> tIndexBuffer ,
1043+ .uInstanceCount = 1 // uCascadeCount
1044+ };
1045+
1046+ gptGfx -> draw_indexed (ptEncoder , 1 , & tDraw );
1047+ * gptData -> pdDrawCalls += 1 ;
1048+
1049+ pl__touch_chunk (ptTerrain , ptChunk );
1050+
1051+ // --- Hysteresis refinement logic ---
1052+ if (fRho > tauSubdivide )
1053+ {
1054+ // Need children soon – schedule them
1055+ for (uint32_t i = 0 ; i < 4 ; i ++ )
1056+ {
1057+ if (ptChunk -> aptChildren [i ])
1058+ pl__request_residency (ptTerrain , ptChunk -> aptChildren [i ]);
1059+ }
1060+ }
1061+ else if (fRho < tauMerge )
1062+ {
1063+ // Clearly low detail – unload children
1064+ pl__unload_children (ptTerrain , ptChunk );
1065+ }
1066+ // else: middle band → keep current state, prevent thrash
1067+ }
1068+ else
1069+ {
1070+ // Descend into children
1071+ for (uint32_t i = 0 ; i < 4 ; i ++ )
1072+ pl__render_chunk_shadow (ptScene , ptTerrain , ptCamera , ptEncoder , ptChunk -> aptChildren [i ], ptFile , ptMVP , uGlobalIndex );
1073+ }
1074+ }
1075+
9981076static bool
9991077pl__terrain_load (plTerrain * ptTerrain , plTerrainProcessInfo * ptInfo )
10001078{
0 commit comments