Display tessellation plans (3/4): display meshes from plans, GPU evaluation in the viewport (#805)
Display meshes are built from tessellation plans, with GPU evaluation in the viewport.
This commit is contained in:
1 parent
534393ed5a
commit
ff7a347013
19 files changed
+1474
-77
No files matched your search
+29
-2
@@ -5,6 +5,7 @@ use std::hash::{Hash, Hasher};
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
use serde::Serialize;
|
||||
use solidcraft_geom::tess::TessPlan;
|
||||
use solidcraft_geom::{Mesh, Plane, Region2, Vec2, Vec3};
|
||||
use solidcraft_kernel::{self as kernel, Body, BoolOp};
|
||||
use solidcraft_sketch::{CurveKind, Profile, SolveReport, find_profiles};
|
||||
@@ -27,6 +28,9 @@ pub struct ModelBody {
|
||||
// Timeline snapshots are cloned before meshes are requested. Share the cell itself so
|
||||
// meshing any snapshot also warms every unchanged copy of this body.
|
||||
mesh: Arc<OnceLock<Arc<Mesh>>>,
|
||||
/// The display plan the mesh is evaluated from (none when the body has no plan: a mesh
|
||||
/// body, or one the planner refuses), shared like the mesh. A GPU viewport draws it.
|
||||
plan: Arc<OnceLock<Option<Arc<TessPlan>>>>,
|
||||
/// Identity of this body's content for the evaluation cache: the key of the evaluation
|
||||
/// that produced it (kept while later features pass it through unchanged).
|
||||
key: u64,
|
||||
@@ -51,6 +55,7 @@ impl ModelBody {
|
||||
body,
|
||||
feature,
|
||||
mesh: Arc::new(OnceLock::new()),
|
||||
plan: Arc::new(OnceLock::new()),
|
||||
key: 0,
|
||||
names: Default::default(),
|
||||
made: Default::default(),
|
||||
@@ -68,12 +73,34 @@ impl ModelBody {
|
||||
pub fn mesh(&self) -> Arc<Mesh> {
|
||||
self.mesh
|
||||
.get_or_init(|| {
|
||||
let tol = (self.body.size() * 1e-3).max(1e-3);
|
||||
Arc::new(self.body.display_mesh_angular(tol, kernel::DISPLAY_ANGLE).unwrap_or_default())
|
||||
let mesh = match self.display_plan() {
|
||||
Some(plan) => self.body.display_mesh_of(&plan),
|
||||
None => self.body.reference_display_mesh_angular(self.display_tol(), kernel::DISPLAY_ANGLE),
|
||||
};
|
||||
Arc::new(mesh.unwrap_or_default())
|
||||
})
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// The display plan behind [`ModelBody::mesh`] (same tolerance and angle), made once; `None`
|
||||
/// when the body has no plan and its mesh comes from the reference mesher.
|
||||
pub fn display_plan(&self) -> Option<Arc<TessPlan>> {
|
||||
self.plan
|
||||
.get_or_init(|| match self.body.display_plan_angular(self.display_tol(), kernel::DISPLAY_ANGLE) {
|
||||
Ok(plan) => Some(Arc::new(plan)),
|
||||
Err(e) => {
|
||||
log::debug!("{}: no display plan ({e})", self.name);
|
||||
None
|
||||
}
|
||||
})
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// The display mesh's chord tolerance: 1/1000 of the body's size.
|
||||
fn display_tol(&self) -> f64 {
|
||||
(self.body.size() * 1e-3).max(1e-3)
|
||||
}
|
||||
|
||||
/// Already computed display geometry; saving must not trigger cold tessellation.
|
||||
pub fn cached_mesh(&self) -> Option<Arc<Mesh>> {
|
||||
self.mesh.get().cloned()
|
||||
|
||||
+32
-12
@@ -970,20 +970,32 @@ pub(crate) fn section(plane: &Plane, m: &Mesh, face: Option<u32>) -> Vec<LinkGeo
|
||||
let size = m.bounds().diagonal().max(1e-9);
|
||||
let q = size * 1e-7;
|
||||
let key = |p: Vec3| ((p.x / q).round() as i64, (p.y / q).round() as i64, (p.z / q).round() as i64);
|
||||
// Surface normal at each mesh vertex where the surface is smooth (None at creases).
|
||||
let mut normals: BTreeMap<(i64, i64, i64), Option<Vec3>> = BTreeMap::new();
|
||||
for (t, _) in m.triangles.iter().zip(&m.tri_face) {
|
||||
// Surface normal at each mesh vertex where the surface is smooth (None at creases), over
|
||||
// all faces and per face.
|
||||
type Key = (i64, i64, i64);
|
||||
let mut normals: BTreeMap<Key, Option<Vec3>> = BTreeMap::new();
|
||||
let mut face_normals: BTreeMap<(u32, Key), Option<Vec3>> = BTreeMap::new();
|
||||
// Faces on either side of each mesh edge.
|
||||
let mut edge_faces: BTreeMap<(Key, Key), Vec<u32>> = BTreeMap::new();
|
||||
let merge = |e: &mut Option<Vec3>, n: Vec3| {
|
||||
if e.is_some_and(|o| o.dot(n) < 1.0 - 1e-6) {
|
||||
*e = None;
|
||||
}
|
||||
};
|
||||
for (t, f) in m.triangles.iter().zip(&m.tri_face) {
|
||||
for k in t {
|
||||
let (Some(p), Some(n)) = (m.positions.get(*k as usize), m.normals.get(*k as usize)) else { continue };
|
||||
let Some(n) = n.normalized() else { continue };
|
||||
normals
|
||||
.entry(key(*p))
|
||||
.and_modify(|e| {
|
||||
if e.is_some_and(|o| o.dot(n) < 1.0 - 1e-6) {
|
||||
*e = None;
|
||||
}
|
||||
})
|
||||
.or_insert(Some(n));
|
||||
normals.entry(key(*p)).and_modify(|e| merge(e, n)).or_insert(Some(n));
|
||||
face_normals.entry((*f, key(*p))).and_modify(|e| merge(e, n)).or_insert(Some(n));
|
||||
}
|
||||
for (i, j) in [(0usize, 1usize), (1, 2), (2, 0)] {
|
||||
let (Some(a), Some(b)) = (t.get(i).and_then(|k| m.positions.get(*k as usize)), t.get(j).and_then(|k| m.positions.get(*k as usize)))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let (ka, kb) = (key(*a), key(*b));
|
||||
edge_faces.entry(if ka <= kb { (ka, kb) } else { (kb, ka) }).or_default().push(*f);
|
||||
}
|
||||
}
|
||||
// Where the plane crosses a mesh edge: on a cubic through the end points whose end
|
||||
@@ -997,8 +1009,16 @@ pub(crate) fn section(plane: &Plane, m: &Mesh, face: Option<u32>) -> Vec<LinkGeo
|
||||
return None;
|
||||
}
|
||||
let d = b - a;
|
||||
// An edge inside one face takes that face's normals (its ends may lie on the face's
|
||||
// boundary, a crease for the whole mesh); an edge between faces, the merged ones, so
|
||||
// both faces agree on the crossing.
|
||||
let inside = edge_faces.get(&(key(a), key(b))).and_then(|fs| fs.first().filter(|f| fs.len() == 2 && fs.iter().all(|x| x == *f)).copied());
|
||||
let normal = |p: Vec3| match inside {
|
||||
Some(f) => face_normals.get(&(f, key(p))).copied().flatten(),
|
||||
None => normals.get(&key(p)).copied().flatten(),
|
||||
};
|
||||
// Tangent length: the arc length of a circle through both ends with these normals.
|
||||
let (na, nb) = (normals.get(&key(a)).copied().flatten(), normals.get(&key(b)).copied().flatten());
|
||||
let (na, nb) = (normal(a), normal(b));
|
||||
let l = match (na, nb) {
|
||||
(Some(x), Some(y)) => {
|
||||
let half = 0.5 * x.dot(y).clamp(-1.0, 1.0).acos();
|
||||
|
||||
Reference in new issue
Block a user