From 9c4043ef45d14e6797226f19eabd1a09a87b07a2 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Wed, 20 May 2026 04:51:26 +0300 Subject: [PATCH] feat(scene): scale Solid3D / Region / Body tessellation by header FACETRES ACIS-bearing entities tessellated with a hard-coded LOD ladder (circ_segs / grid_u / grid_v) so a high-detail drawing produced the same coarse mesh as a low-detail one. Multiply the per-LOD counts by `header.facet_resolution` (FACETRES), clamped to AutoCAD's documented [0.01, 10.0] range with a floor of 4 segments to keep degenerate values harmless. All four call sites (build_caches, commit_entity mesh seed, undo/redo populate_meshes_from_document, and the helper signatures themselves) take a FACETRES argument now. ISOLINES / surface_u_density / surface_v_density and the camera / sun / geo header fields stay metadata until matching render work lands. Co-Authored-By: Claude Opus 4.7 --- src/scene/mod.rs | 27 +++++++++++++++++---------- src/scene/solid3d_tess.rs | 29 ++++++++++++++++++++++------- 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 5a2e0e1d4..8baad7e76 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -203,16 +203,19 @@ pub fn build_derived_caches(doc: &CadDocument) -> DerivedCaches { }) .collect(); - // meshes (parallel tessellation) + // meshes (parallel tessellation). FACETRES (header.facet_resolution) + // scales the per-LOD segment counts so users with finer drawings get + // smoother solids; clamped to AutoCAD's [0.01, 10.0] range inside. + let facet_res = doc.header.facet_resolution; let meshes: HashMap = mesh_handles .par_iter() .filter_map(|&handle| { let e = doc.get_entity(handle)?; let color = tessellate::aci_to_rgba(&e.common().color); let model = match e { - EntityType::Solid3D(s) => solid3d_tess::tessellate_solid3d(s, color), - EntityType::Region(r) => solid3d_tess::tessellate_region(r, color), - EntityType::Body(b) => solid3d_tess::tessellate_body(b, color), + EntityType::Solid3D(s) => solid3d_tess::tessellate_solid3d(s, color, facet_res), + EntityType::Region(r) => solid3d_tess::tessellate_region(r, color, facet_res), + EntityType::Body(b) => solid3d_tess::tessellate_body(b, color, facet_res), _ => None, }; model.map(|m| (handle, m)) @@ -2372,18 +2375,19 @@ impl Scene { } else { None }; + let facet_res = self.document.header.facet_resolution; let mesh_seed = match &entity { EntityType::Solid3D(s3d) => { let color = self.render_style(&entity).0; - solid3d_tess::tessellate_solid3d(s3d, color) + solid3d_tess::tessellate_solid3d(s3d, color, facet_res) } EntityType::Region(r) => { let color = self.render_style(&entity).0; - solid3d_tess::tessellate_region(r, color) + solid3d_tess::tessellate_region(r, color, facet_res) } EntityType::Body(b) => { let color = self.render_style(&entity).0; - solid3d_tess::tessellate_body(b, color) + solid3d_tess::tessellate_body(b, color, facet_res) } _ => None, }; @@ -3216,13 +3220,16 @@ impl Scene { .collect(); use rayon::prelude::*; + let facet_res = self.document.header.facet_resolution; self.meshes = entries .into_par_iter() .filter_map(|(handle, entity, color)| { let model = match &entity { - EntityType::Solid3D(s) => solid3d_tess::tessellate_solid3d(s, color), - EntityType::Region(r) => solid3d_tess::tessellate_region(r, color), - EntityType::Body(b) => solid3d_tess::tessellate_body(b, color), + EntityType::Solid3D(s) => { + solid3d_tess::tessellate_solid3d(s, color, facet_res) + } + EntityType::Region(r) => solid3d_tess::tessellate_region(r, color, facet_res), + EntityType::Body(b) => solid3d_tess::tessellate_body(b, color, facet_res), _ => None, }; model.map(|m| (handle, m)) diff --git a/src/scene/solid3d_tess.rs b/src/scene/solid3d_tess.rs index b7066acb7..71ff8ba88 100644 --- a/src/scene/solid3d_tess.rs +++ b/src/scene/solid3d_tess.rs @@ -120,11 +120,13 @@ fn tessellate_sat_lods( sat: &SatDocument, name: String, color: [f32; 4], + facet_res: f64, ) -> Option { let configs = LodConfig::all(); let mut lods: Vec = Vec::with_capacity(3); for lod in configs { - if let Some(m) = tessellate_sat(sat, name.clone(), color, lod) { + let scaled = scale_lod(lod, facet_res); + if let Some(m) = tessellate_sat(sat, name.clone(), color, scaled) { lods.push(m); } } @@ -135,6 +137,18 @@ fn tessellate_sat_lods( Some(MeshLodSet { lods, world_aabb }) } +/// Scale a LOD's segment counts by FACETRES (clamped to AutoCAD's +/// documented [0.01, 10.0] range). 1.0 is the unchanged baseline. +fn scale_lod(base: LodConfig, facet_res: f64) -> LodConfig { + let m = (facet_res.clamp(0.01, 10.0) as f32).max(0.01); + let scale = |v: usize| ((v as f32) * m).round().max(4.0) as usize; + LodConfig { + circ_segs: scale(base.circ_segs), + grid_u: scale(base.grid_u), + grid_v: scale(base.grid_v), + } +} + /// World-XY AABB of the mesh — used by the render-pipeline LOD selector /// to pick a level based on projected pixel diagonal. fn mesh_aabb(mesh: &MeshModel) -> [f32; 4] { @@ -169,39 +183,40 @@ fn parse_acis( } /// Tessellate a `Region` entity (2D planar ACIS body) at all three LOD levels. -pub fn tessellate_region(region: &Region, color: [f32; 4]) -> Option { +pub fn tessellate_region(region: &Region, color: [f32; 4], facet_res: f64) -> Option { let sat = parse_acis( || region.parse_sat(), region.acis_data.is_binary, ®ion.acis_data.sab_data, )?; let name = region.common.handle.value().to_string(); - tessellate_sat_lods(&sat, name, color) + tessellate_sat_lods(&sat, name, color, facet_res) } /// Tessellate a `Body` entity (3D ACIS body) at all three LOD levels. -pub fn tessellate_body(body: &Body, color: [f32; 4]) -> Option { +pub fn tessellate_body(body: &Body, color: [f32; 4], facet_res: f64) -> Option { let sat = parse_acis( || body.parse_sat(), body.acis_data.is_binary, &body.acis_data.sab_data, )?; let name = body.common.handle.value().to_string(); - tessellate_sat_lods(&sat, name, color) + tessellate_sat_lods(&sat, name, color, facet_res) } /// Tessellate a `Solid3D` entity at all three LOD levels. /// /// Returns `None` when the entity has no parseable SAT data or produces no /// triangles (e.g. the solid uses only unsupported surface types). -pub fn tessellate_solid3d(solid: &Solid3D, color: [f32; 4]) -> Option { +/// `facet_res` mirrors the header FACETRES variable (0.01–10.0). +pub fn tessellate_solid3d(solid: &Solid3D, color: [f32; 4], facet_res: f64) -> Option { let sat = parse_acis( || solid.parse_sat(), solid.acis_data.is_binary, &solid.acis_data.sab_data, )?; let name = solid.common.handle.value().to_string(); - tessellate_sat_lods(&sat, name, color) + tessellate_sat_lods(&sat, name, color, facet_res) } // ── Topology helpers ──────────────────────────────────────────────────────────