perf(scene): multi-resolution mesh LOD (Phase 3.4)
ACIS SAT entities (Solid3D / Region / Body) were tessellated at one
fixed sampling density (CIRC_SEGS=48, GRID_U=32, GRID_V=16) regardless
of how far they were from the camera. Distant solids paid the full
triangle bill for fragments that mostly resolved to sub-pixel size.
Generate three pre-built LODs per entity at load time:
* LOD 0 (HIGH) — 48/32/16 segments. Use above 200 px projected diag.
* LOD 1 (MID) — 24/16/8. Use 50–200 px.
* LOD 2 (LOW) — 12/8/4. Use below 50 px.
Tessellation cost is paid 3× on load (cheap — typical files have few
solids). Render picks per-frame via `compute_mesh_lod` from each mesh's
`world_aabb` projected diagonal. When a level is missing (e.g. a solid
entity that only emits LOD 0), the selector walks down to the nearest
available slot.
API ripples:
* New `LodConfig` (HIGH/MID/LOW) drives `tess_sat`, `tess_cone_face`,
`tess_sphere_face`, `tess_torus_face`.
* `tessellate_solid3d / _region / _body` now return `MeshLodSet`.
* `MeshLodSet { lods: Vec<MeshModel>, world_aabb }` + `from_single`
constructor so interactive truck-based commands (BOX/CYLINDER/etc.)
that only generate one tessellation stay one-line.
* GPU side gets `MeshLodGpu { lods: Vec<MeshGpu>, world_aabb }` and
a `mesh_lod_levels` per-frame index vector.
* STL/STEP export pulls slot 0 explicitly so the on-disk geometry
isn't downgraded by the view-dependent ladder.
Background-thread lazy generation (per the roadmap) is deferred — the
synchronous eager build is fine for typical CAD files. Switch to a
queue + background tessellator when files with hundreds of solids
become a load-time bottleneck.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
e7675be0bc
commit
8c08ae104a
8 changed files with 321 additions and 59 deletions
|
|
@ -7,7 +7,7 @@
|
|||
// ------
|
||||
// 40 B / vertex
|
||||
|
||||
use crate::scene::mesh_model::MeshModel;
|
||||
use crate::scene::mesh_model::{MeshLodSet, MeshModel};
|
||||
use iced::wgpu;
|
||||
use iced::wgpu::util::DeviceExt;
|
||||
|
||||
|
|
@ -55,6 +55,27 @@ pub struct MeshGpu {
|
|||
pub index_count: u32,
|
||||
}
|
||||
|
||||
/// GPU-side bundle of MeshLodSet — one MeshGpu per available LOD plus
|
||||
/// the world-XY AABB needed to pick a level per frame.
|
||||
pub struct MeshLodGpu {
|
||||
pub lods: Vec<MeshGpu>,
|
||||
pub world_aabb: [f32; 4],
|
||||
}
|
||||
|
||||
impl MeshLodGpu {
|
||||
pub fn new(device: &wgpu::Device, set: &MeshLodSet) -> Self {
|
||||
Self {
|
||||
lods: set
|
||||
.lods
|
||||
.iter()
|
||||
.filter(|m| !m.indices.is_empty())
|
||||
.map(|m| MeshGpu::new(device, m))
|
||||
.collect(),
|
||||
world_aabb: set.world_aabb,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MeshGpu {
|
||||
pub fn new(device: &wgpu::Device, mesh: &MeshModel) -> Self {
|
||||
let has_normals = mesh.normals.len() == mesh.verts.len();
|
||||
|
|
|
|||
Loading…
Reference in a new issue