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 <noreply@anthropic.com>
This commit is contained in:
parent
0db21568c1
commit
9c4043ef45
2 changed files with 39 additions and 17 deletions
|
|
@ -203,16 +203,19 @@ pub fn build_derived_caches(doc: &CadDocument) -> DerivedCaches {
|
||||||
})
|
})
|
||||||
.collect();
|
.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<Handle, MeshLodSet> = mesh_handles
|
let meshes: HashMap<Handle, MeshLodSet> = mesh_handles
|
||||||
.par_iter()
|
.par_iter()
|
||||||
.filter_map(|&handle| {
|
.filter_map(|&handle| {
|
||||||
let e = doc.get_entity(handle)?;
|
let e = doc.get_entity(handle)?;
|
||||||
let color = tessellate::aci_to_rgba(&e.common().color);
|
let color = tessellate::aci_to_rgba(&e.common().color);
|
||||||
let model = match e {
|
let model = match e {
|
||||||
EntityType::Solid3D(s) => solid3d_tess::tessellate_solid3d(s, color),
|
EntityType::Solid3D(s) => solid3d_tess::tessellate_solid3d(s, color, facet_res),
|
||||||
EntityType::Region(r) => solid3d_tess::tessellate_region(r, color),
|
EntityType::Region(r) => solid3d_tess::tessellate_region(r, color, facet_res),
|
||||||
EntityType::Body(b) => solid3d_tess::tessellate_body(b, color),
|
EntityType::Body(b) => solid3d_tess::tessellate_body(b, color, facet_res),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
model.map(|m| (handle, m))
|
model.map(|m| (handle, m))
|
||||||
|
|
@ -2372,18 +2375,19 @@ impl Scene {
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
let facet_res = self.document.header.facet_resolution;
|
||||||
let mesh_seed = match &entity {
|
let mesh_seed = match &entity {
|
||||||
EntityType::Solid3D(s3d) => {
|
EntityType::Solid3D(s3d) => {
|
||||||
let color = self.render_style(&entity).0;
|
let color = self.render_style(&entity).0;
|
||||||
solid3d_tess::tessellate_solid3d(s3d, color)
|
solid3d_tess::tessellate_solid3d(s3d, color, facet_res)
|
||||||
}
|
}
|
||||||
EntityType::Region(r) => {
|
EntityType::Region(r) => {
|
||||||
let color = self.render_style(&entity).0;
|
let color = self.render_style(&entity).0;
|
||||||
solid3d_tess::tessellate_region(r, color)
|
solid3d_tess::tessellate_region(r, color, facet_res)
|
||||||
}
|
}
|
||||||
EntityType::Body(b) => {
|
EntityType::Body(b) => {
|
||||||
let color = self.render_style(&entity).0;
|
let color = self.render_style(&entity).0;
|
||||||
solid3d_tess::tessellate_body(b, color)
|
solid3d_tess::tessellate_body(b, color, facet_res)
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
@ -3216,13 +3220,16 @@ impl Scene {
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
|
let facet_res = self.document.header.facet_resolution;
|
||||||
self.meshes = entries
|
self.meshes = entries
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
.filter_map(|(handle, entity, color)| {
|
.filter_map(|(handle, entity, color)| {
|
||||||
let model = match &entity {
|
let model = match &entity {
|
||||||
EntityType::Solid3D(s) => solid3d_tess::tessellate_solid3d(s, color),
|
EntityType::Solid3D(s) => {
|
||||||
EntityType::Region(r) => solid3d_tess::tessellate_region(r, color),
|
solid3d_tess::tessellate_solid3d(s, color, facet_res)
|
||||||
EntityType::Body(b) => solid3d_tess::tessellate_body(b, color),
|
}
|
||||||
|
EntityType::Region(r) => solid3d_tess::tessellate_region(r, color, facet_res),
|
||||||
|
EntityType::Body(b) => solid3d_tess::tessellate_body(b, color, facet_res),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
model.map(|m| (handle, m))
|
model.map(|m| (handle, m))
|
||||||
|
|
|
||||||
|
|
@ -120,11 +120,13 @@ fn tessellate_sat_lods(
|
||||||
sat: &SatDocument,
|
sat: &SatDocument,
|
||||||
name: String,
|
name: String,
|
||||||
color: [f32; 4],
|
color: [f32; 4],
|
||||||
|
facet_res: f64,
|
||||||
) -> Option<MeshLodSet> {
|
) -> Option<MeshLodSet> {
|
||||||
let configs = LodConfig::all();
|
let configs = LodConfig::all();
|
||||||
let mut lods: Vec<MeshModel> = Vec::with_capacity(3);
|
let mut lods: Vec<MeshModel> = Vec::with_capacity(3);
|
||||||
for lod in configs {
|
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);
|
lods.push(m);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -135,6 +137,18 @@ fn tessellate_sat_lods(
|
||||||
Some(MeshLodSet { lods, world_aabb })
|
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
|
/// World-XY AABB of the mesh — used by the render-pipeline LOD selector
|
||||||
/// to pick a level based on projected pixel diagonal.
|
/// to pick a level based on projected pixel diagonal.
|
||||||
fn mesh_aabb(mesh: &MeshModel) -> [f32; 4] {
|
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.
|
/// Tessellate a `Region` entity (2D planar ACIS body) at all three LOD levels.
|
||||||
pub fn tessellate_region(region: &Region, color: [f32; 4]) -> Option<MeshLodSet> {
|
pub fn tessellate_region(region: &Region, color: [f32; 4], facet_res: f64) -> Option<MeshLodSet> {
|
||||||
let sat = parse_acis(
|
let sat = parse_acis(
|
||||||
|| region.parse_sat(),
|
|| region.parse_sat(),
|
||||||
region.acis_data.is_binary,
|
region.acis_data.is_binary,
|
||||||
®ion.acis_data.sab_data,
|
®ion.acis_data.sab_data,
|
||||||
)?;
|
)?;
|
||||||
let name = region.common.handle.value().to_string();
|
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.
|
/// Tessellate a `Body` entity (3D ACIS body) at all three LOD levels.
|
||||||
pub fn tessellate_body(body: &Body, color: [f32; 4]) -> Option<MeshLodSet> {
|
pub fn tessellate_body(body: &Body, color: [f32; 4], facet_res: f64) -> Option<MeshLodSet> {
|
||||||
let sat = parse_acis(
|
let sat = parse_acis(
|
||||||
|| body.parse_sat(),
|
|| body.parse_sat(),
|
||||||
body.acis_data.is_binary,
|
body.acis_data.is_binary,
|
||||||
&body.acis_data.sab_data,
|
&body.acis_data.sab_data,
|
||||||
)?;
|
)?;
|
||||||
let name = body.common.handle.value().to_string();
|
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.
|
/// Tessellate a `Solid3D` entity at all three LOD levels.
|
||||||
///
|
///
|
||||||
/// Returns `None` when the entity has no parseable SAT data or produces no
|
/// Returns `None` when the entity has no parseable SAT data or produces no
|
||||||
/// triangles (e.g. the solid uses only unsupported surface types).
|
/// triangles (e.g. the solid uses only unsupported surface types).
|
||||||
pub fn tessellate_solid3d(solid: &Solid3D, color: [f32; 4]) -> Option<MeshLodSet> {
|
/// `facet_res` mirrors the header FACETRES variable (0.01–10.0).
|
||||||
|
pub fn tessellate_solid3d(solid: &Solid3D, color: [f32; 4], facet_res: f64) -> Option<MeshLodSet> {
|
||||||
let sat = parse_acis(
|
let sat = parse_acis(
|
||||||
|| solid.parse_sat(),
|
|| solid.parse_sat(),
|
||||||
solid.acis_data.is_binary,
|
solid.acis_data.is_binary,
|
||||||
&solid.acis_data.sab_data,
|
&solid.acis_data.sab_data,
|
||||||
)?;
|
)?;
|
||||||
let name = solid.common.handle.value().to_string();
|
let name = solid.common.handle.value().to_string();
|
||||||
tessellate_sat_lods(&sat, name, color)
|
tessellate_sat_lods(&sat, name, color, facet_res)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Topology helpers ──────────────────────────────────────────────────────────
|
// ── Topology helpers ──────────────────────────────────────────────────────────
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue