perf(scene): zoom-adaptive curve tolerance for top-level wires (Phase 3.2)
Edge tessellation used a fixed 0.005 world-unit chord-height tolerance regardless of zoom — a 1000 m radius arc viewed at 50 mm/px still emitted hundreds of segments that all collapsed onto the same screen pixel. `truck_tess` now reads tolerance from a process-wide AtomicU64 override. `Scene::wires_for_block` sets the override to ~0.5 px chord height (world_per_pixel × 0.5) before the par_iter, then a Drop guard clears it back to the default. Floor stays at the original CURVE_TOL so extreme zoom-in keeps full quality. Block-cache rebuilds and off-render paths (snap, hit-test) always see the default — the override is bracketed to the render call only. Roadmap entry for Phase 3.2 removed; Implementation Order / Key Files tables updated. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
0584e3c862
commit
26271b8c63
3 changed files with 50 additions and 29 deletions
|
|
@ -64,31 +64,6 @@ let candidates = scene.quadtree.query_rect(view_aabb);
|
||||||
|
|
||||||
**Goal:** Reduce geometric complexity when entities are small or far away.
|
**Goal:** Reduce geometric complexity when entities are small or far away.
|
||||||
|
|
||||||
### 3.2 Wire LOD — Curve Segment Reduction
|
|
||||||
|
|
||||||
Arcs, ellipses, and splines tessellate to N segments (currently fixed). Replace with
|
|
||||||
zoom-adaptive segment count.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
fn arc_segments(radius_world: f64, px_per_unit: f64) -> u32 {
|
|
||||||
// target: ~1 segment per 2px of arc length on screen
|
|
||||||
let arc_px = radius_world * px_per_unit * TWO_PI;
|
|
||||||
(arc_px / 2.0).clamp(8.0, 256.0) as u32
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Recompute segments when zoom crosses a threshold (epoch-based invalidation already in place).
|
|
||||||
Store LOD level per entity; retessellate only on LOD level change.
|
|
||||||
|
|
||||||
LOD levels (example):
|
|
||||||
|
|
||||||
| Zoom (px/unit) | Segments per 90° arc |
|
|
||||||
|----------------|----------------------|
|
|
||||||
| > 100 px/u | 64 (full detail) |
|
|
||||||
| 10–100 px/u | 24 |
|
|
||||||
| 1–10 px/u | 12 |
|
|
||||||
| < 1 px/u | 6 (or sub-pixel cull)|
|
|
||||||
|
|
||||||
### 3.3 Hatch LOD — Density Reduction
|
### 3.3 Hatch LOD — Density Reduction
|
||||||
|
|
||||||
At low zoom, hatch line spacing appears smaller than 1px. Options:
|
At low zoom, hatch line spacing appears smaller than 1px. Options:
|
||||||
|
|
@ -163,7 +138,6 @@ Relevant only for perspective (3D) mode with many overlapping solids.
|
||||||
```
|
```
|
||||||
Phase 1.4 Scissor for hatch/image low complexity
|
Phase 1.4 Scissor for hatch/image low complexity
|
||||||
Phase 2.1 Quadtree for 2D medium complexity, scales 2D docs
|
Phase 2.1 Quadtree for 2D medium complexity, scales 2D docs
|
||||||
Phase 3.2 Wire curve LOD medium, affects tessellation cache
|
|
||||||
Phase 3.3 Hatch LOD low complexity
|
Phase 3.3 Hatch LOD low complexity
|
||||||
Phase 3.4 Mesh LOD high complexity, background thread
|
Phase 3.4 Mesh LOD high complexity, background thread
|
||||||
Phase 2.2 Octree for 3D medium, needed for dense 3D
|
Phase 2.2 Octree for 3D medium, needed for dense 3D
|
||||||
|
|
@ -180,7 +154,6 @@ Phase 4.2 Hi-Z occlusion high complexity, 3D only, last
|
||||||
| `src/scene/mesh_model.rs` | Add `Aabb3`, LOD mesh array |
|
| `src/scene/mesh_model.rs` | Add `Aabb3`, LOD mesh array |
|
||||||
| `src/scene/hatch_model.rs` | Add `Aabb2` field |
|
| `src/scene/hatch_model.rs` | Add `Aabb2` field |
|
||||||
| `src/scene/image_model.rs` | Add `Aabb2` field |
|
| `src/scene/image_model.rs` | Add `Aabb2` field |
|
||||||
| `src/scene/tessellate.rs` | Adaptive arc segments |
|
|
||||||
| `src/scene/truck_tess.rs` | Multi-resolution mesh tessellation |
|
| `src/scene/truck_tess.rs` | Multi-resolution mesh tessellation |
|
||||||
| `src/scene/mod.rs` | Quadtree/octree; LOD epoch tracking |
|
| `src/scene/mod.rs` | Quadtree/octree; LOD epoch tracking |
|
||||||
| `src/scene/pipeline/mod.rs` | Cull hatch/image entities before upload loops |
|
| `src/scene/pipeline/mod.rs` | Cull hatch/image entities before upload loops |
|
||||||
|
|
|
||||||
|
|
@ -1051,6 +1051,21 @@ impl Scene {
|
||||||
let blk_ref: &block_cache::BlockCache = &blk_cache;
|
let blk_ref: &block_cache::BlockCache = &blk_cache;
|
||||||
let view_aabb = self.view_world_aabb();
|
let view_aabb = self.view_world_aabb();
|
||||||
let wpp = self.world_per_pixel();
|
let wpp = self.world_per_pixel();
|
||||||
|
// Zoom-adaptive curve sampling for top-level Edge tessellation. Target
|
||||||
|
// ~0.5 px chord height — far-out arcs that used to emit hundreds of
|
||||||
|
// segments now collapse to a handful. The guard clears the override
|
||||||
|
// when this scope exits so off-render tessellation (snap previews,
|
||||||
|
// hit-test, block_cache rebuild) sees the default.
|
||||||
|
struct CurveTolGuard;
|
||||||
|
impl Drop for CurveTolGuard {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
crate::scene::truck_tess::set_curve_tol_override(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let _tol_guard = wpp.map(|w| {
|
||||||
|
crate::scene::truck_tess::set_curve_tol_override(Some((w * 0.5) as f64));
|
||||||
|
CurveTolGuard
|
||||||
|
});
|
||||||
let mut wires: Vec<WireModel> = visible
|
let mut wires: Vec<WireModel> = visible
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
.flat_map(|e| {
|
.flat_map(|e| {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,38 @@ const CURVE_TOL: f64 = 0.005;
|
||||||
// Triangle mesh tolerance (world units).
|
// Triangle mesh tolerance (world units).
|
||||||
pub const MESH_TOL: f64 = 0.01;
|
pub const MESH_TOL: f64 = 0.01;
|
||||||
|
|
||||||
|
// ── Zoom-adaptive curve tolerance override ────────────────────────────────
|
||||||
|
//
|
||||||
|
// The Scene sets this once per render frame to a value derived from
|
||||||
|
// `world_per_pixel` (target ≈ 0.5 px chord height). All Edge tessellations
|
||||||
|
// inside the frame — including those running on rayon worker threads — read
|
||||||
|
// from the same atomic, so callers don't need to thread a tolerance
|
||||||
|
// parameter through every entity-converter signature. Zero means "use
|
||||||
|
// `CURVE_TOL`"; that is the default, and what BlockCache::build expects.
|
||||||
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
static CURVE_TOL_BITS: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
|
/// Set the per-frame curve tolerance. `None` (or any non-finite/<=0 value)
|
||||||
|
/// reverts to the default `CURVE_TOL`.
|
||||||
|
pub fn set_curve_tol_override(tol: Option<f64>) {
|
||||||
|
let bits = match tol {
|
||||||
|
Some(t) if t > 0.0 && t.is_finite() => t.to_bits(),
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
CURVE_TOL_BITS.store(bits, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolve the active curve tolerance. Clamped to the floor `CURVE_TOL` so
|
||||||
|
/// extreme zoom-in never under-samples below the existing baseline quality.
|
||||||
|
fn current_curve_tol() -> f64 {
|
||||||
|
let bits = CURVE_TOL_BITS.load(Ordering::Relaxed);
|
||||||
|
if bits == 0 {
|
||||||
|
CURVE_TOL
|
||||||
|
} else {
|
||||||
|
f64::from_bits(bits).max(CURVE_TOL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Public result type ────────────────────────────────────────────────────
|
// ── Public result type ────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// Output of any truck topology tessellation.
|
/// Output of any truck topology tessellation.
|
||||||
|
|
@ -65,8 +97,9 @@ pub fn tessellate_edge(e: &Edge, offset: [f64; 3]) -> TruckTessResult {
|
||||||
let curve = e.oriented_curve();
|
let curve = e.oriented_curve();
|
||||||
let (t0, t1) = curve.range_tuple();
|
let (t0, t1) = curve.range_tuple();
|
||||||
// parameter_division samples adaptively: fewer points on straight segments,
|
// parameter_division samples adaptively: fewer points on straight segments,
|
||||||
// more on tight curves, all within CURVE_TOL chord-height error.
|
// more on tight curves, all within the active chord-height tolerance.
|
||||||
let (_, pts) = curve.parameter_division((t0, t1), CURVE_TOL);
|
// The Scene scales this with zoom so far-out arcs aren't oversampled.
|
||||||
|
let (_, pts) = curve.parameter_division((t0, t1), current_curve_tol());
|
||||||
let lines = pts
|
let lines = pts
|
||||||
.iter()
|
.iter()
|
||||||
.map(|p| to_local(p.x, p.y, p.z, offset))
|
.map(|p| to_local(p.x, p.y, p.z, offset))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue