Skip to content

Terrain LOD refactor: edge stitching, hybrid mesh LOD, performance fixes#87

Open
brendancol wants to merge 4 commits intomasterfrom
lod-refactor
Open

Terrain LOD refactor: edge stitching, hybrid mesh LOD, performance fixes#87
brendancol wants to merge 4 commits intomasterfrom
lod-refactor

Conversation

@brendancol
Copy link
Contributor

@brendancol brendancol commented Mar 9, 2026

Summary

  • Edge stitching replaces tile skirts: boundary vertex Z values are interpolated from the coarser neighbor's pyramid data via _stitch_tile_boundary(), eliminating T-junction cracks without extra geometry. _needs_stitch() fast-check avoids vertex copies when no neighbor differs.
  • Hybrid LOD for placed geometry: _MeshChunkManager applies simplify_mesh() during merge based on chunk LOD level (LOD 0 = full detail, LOD 1 = 50%, LOD 2 = 25%, LOD 3+ = 10%). Lazy cached in _simplify_cache, evicted when chunks leave visible set. Curves excluded.
  • Critical bugfix: _rebuild_vertical_exaggeration() was missing cache_key definition — caused NameError crash on VE change (Shift+Home/End) with non-LOD terrain.
  • Performance fixes: early frustum culling before LOD/roughness computation (~40-60% fewer evaluations), single-pass future harvesting, O(1) stale tile eviction, simplified Z re-snapping (final_z = stored_z * ve), GPU threshold bumped 1K→10K, in-place cache eviction, pyramid level eviction above max_lod, float32 roughness computation.
  • Robustness: build retry budget (max 3 attempts with logging), I/O prefetch rate limit (max 8/tick), get_metrics() structured API, _has_in_flight_work rename for clarity.
  • Code cleanup: removed dead _add_tile_skirt function, extracted update() into focused helper phases (A-E), updated docstrings and mesh.py references.
  • Tests: 114 tests (up from ~30) covering edge stitching Z-value verification, hysteresis dead zone, retry budget, mesh simplification, heightfield integration, streaming, batched upload, and roughness-adaptive LOD.

Test plan

  • pytest rtxpy/tests/test_lod.py — 114 tests pass
  • Manual: toggle LOD (Shift+A) on a large zarr terrain, verify no T-junction cracks at tile boundaries
  • Manual: change VE (Shift+Home/End) on non-LOD terrain — should no longer crash
  • Manual: verify placed geometry (buildings/roads) simplifies at distance with chunk manager active
  • Manual: streaming mode — verify I/O prefetch doesn't flood thread pool on initial load

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
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant