Skip to content

Commit 9e3abdf

Browse files
authored
Terrain LOD refactor: edge stitching, hybrid mesh LOD, performance fixes (#87)
* Terrain LOD refactor: edge stitching, hybrid mesh LOD, performance fixes Major enhancements to the terrain LOD system: - Replace tile skirts with boundary vertex stitching (_stitch_tile_boundary) that interpolates Z from coarser neighbor's pyramid data, eliminating T-junction cracks without extra geometry - Hybrid LOD for placed geometry: chunk manager applies simplify_mesh() during merge based on chunk LOD level (LOD 0=full, LOD 3=10% triangles) - Extract update() into focused helper phases (A-E) reducing main method from 324 to ~194 lines - Fix critical NameError: add missing cache_key in _rebuild_vertical_exaggeration - Add build retry budget (max 3 attempts) with logging for failed tile builds - Rate-limit I/O prefetches (max 8/tick) to prevent thread pool flooding - Early frustum culling before LOD/roughness computation (~40-60% fewer evaluations for tiles behind camera) - Single-pass future harvesting in _collect_completed_builds - Pyramid cache eviction for levels above max_lod - In-place simplify cache eviction (del loop vs dict rebuild) - O(1) stale tile eviction via set lookup - Simplified Z re-snapping: final_z = stored_z * ve - GPU re-snap threshold bumped from 1K to 10K verts - Float32 precision for compute_tile_roughness bilinear interpolation - Remove dead _add_tile_skirt function and references - Add get_metrics() structured API for programmatic LOD monitoring - Rename _has_pending to _has_in_flight_work for clarity - 114 tests (up from ~30), including edge stitching Z-value verification, hysteresis dead zone coverage, retry budget, and mesh simplification * general sync up * Fix ocean-fill NaN replacement crashing on dask-backed rasters Use the already-computed numpy array (base_np) instead of copying the raw raster data, which may be a dask array that doesn't support boolean mask assignment. * quick fix for scenes
1 parent 5e18e04 commit 9e3abdf

26 files changed

+7908
-1998
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,4 @@ examples/.ipynb_checkpoints/
4545
conda-output/
4646
examples/cache/
4747
*gtfs.json
48+
.claude/worktrees/

cuda/common.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,6 @@ struct Params
6868
int _pad0; // padding for pointer alignment
6969
// --- point cloud fields (offset 88) ---
7070
float* point_colors; // per-point RGBA (4 floats per point, indexed by primitive_id)
71+
// --- smooth normal fields (offset 96) ---
72+
unsigned long long* smooth_normal_table; // [2*instanceId]=normals_ptr, [2*instanceId+1]=indices_ptr
7173
};

cuda/kernel.cu

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -99,17 +99,46 @@ extern "C" __global__ void __closesthit__chit()
9999

100100
float3 n;
101101
if (optixIsTriangleHit()) {
102-
float3 data[3];
103-
// Always use the 4-parameter overload for backward compatibility.
104-
// The parameterless overload (OptiX 9.1+) requires ABI version 99,
105-
// which needs driver 570+. The 4-param form works on all versions.
106-
OptixTraversableHandle gas = optixGetGASTraversableHandle();
107-
unsigned int sbtIdx = optixGetSbtGASIndex();
108-
float time = optixGetRayTime();
109-
optixGetTriangleVertexData(gas, primIdx, sbtIdx, time, data);
110-
float3 AB = data[1] - data[0];
111-
float3 AC = data[2] - data[0];
112-
n = normalize(cross(AB, AC));
102+
// Check for per-vertex smooth normals for this instance
103+
bool has_smooth = false;
104+
if (params.smooth_normal_table != 0) {
105+
unsigned int instId = optixGetInstanceId();
106+
unsigned long long normals_ptr = params.smooth_normal_table[2 * instId];
107+
if (normals_ptr != 0) {
108+
has_smooth = true;
109+
unsigned long long indices_ptr = params.smooth_normal_table[2 * instId + 1];
110+
const float* norms = reinterpret_cast<const float*>(normals_ptr);
111+
const int* idx = reinterpret_cast<const int*>(indices_ptr);
112+
113+
// Barycentric interpolation of vertex normals
114+
const float2 bary = optixGetTriangleBarycentrics();
115+
const float w0 = 1.0f - bary.x - bary.y;
116+
const float w1 = bary.x;
117+
const float w2 = bary.y;
118+
119+
const int i0 = idx[primIdx * 3];
120+
const int i1 = idx[primIdx * 3 + 1];
121+
const int i2 = idx[primIdx * 3 + 2];
122+
123+
n = normalize(make_float3(
124+
w0 * norms[i0 * 3] + w1 * norms[i1 * 3] + w2 * norms[i2 * 3],
125+
w0 * norms[i0 * 3 + 1] + w1 * norms[i1 * 3 + 1] + w2 * norms[i2 * 3 + 1],
126+
w0 * norms[i0 * 3 + 2] + w1 * norms[i1 * 3 + 2] + w2 * norms[i2 * 3 + 2]
127+
));
128+
}
129+
}
130+
131+
if (!has_smooth) {
132+
// Flat shading fallback: face normal from triangle vertices
133+
float3 data[3];
134+
OptixTraversableHandle gas = optixGetGASTraversableHandle();
135+
unsigned int sbtIdx = optixGetSbtGASIndex();
136+
float time = optixGetRayTime();
137+
optixGetTriangleVertexData(gas, primIdx, sbtIdx, time, data);
138+
float3 AB = data[1] - data[0];
139+
float3 AC = data[2] - data[0];
140+
n = normalize(cross(AB, AC));
141+
}
113142
} else {
114143
// Round curve tube: use face-up normal for terrain roads/rivers
115144
n = make_float3(0.0f, 0.0f, 1.0f);

0 commit comments

Comments
 (0)