Hovered and selected faces light solidly again over GPU-drawn bodies (#1064)

Since the model's mesh went back to the reference mesher (#940), the GPU viewport drew
opaque bodies from their display plans while hover and selection lit faces with the
reference mesh's triangles. Two triangulations of the same face at equal depth fought, so
a hovered face shimmered in wedges instead of lighting whole.

Faces lit over a body drawn from its plan now use the plan's own triangles
(ModelBody::plan_mesh, evaluated once on the CPU), and the highlight pass gets a small
depth bias toward the eye so f32 rounding between the GPU and CPU evaluations of the same
triangles cannot speckle it either.

Test: every face of a box with a pocket and a round boss, hovered and rendered offscreen
through the viewport's real pipelines on the GPU path, lights the same pixels as on the
CPU path (skipped without a GPU adapter).

Co-authored-by: Claude Opus 5.5 <[email protected]>
This commit is contained in:
bherbruckandClaude Opus 5.5 authored and GitHub committed 2026-10-14 08:56:35 -04:00
1 parent b5efde94c8
commit 75f1cdd4c9
3 files changed
+162 -22

No files matched your search

+12
View File
@@ -31,6 +31,9 @@ pub struct ModelBody {
/// The display plan (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>>>>,
/// The plan evaluated on the CPU (none without a plan), shared like the mesh: the triangles a
/// GPU viewport draws, so a highlight over a face lies exactly on it.
plan_mesh: Arc<OnceLock<Option<Arc<Mesh>>>>,
/// 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,
@@ -59,6 +62,7 @@ impl ModelBody {
feature,
mesh: Arc::new(OnceLock::new()),
plan: Arc::new(OnceLock::new()),
plan_mesh: Arc::new(OnceLock::new()),
key: 0,
names: Default::default(),
made: Default::default(),
@@ -98,6 +102,14 @@ impl ModelBody {
.clone()
}
/// The display plan's mesh, evaluated on the CPU once: the same triangles a GPU viewport
/// evaluates and draws for this body. Highlights over a face drawn from the plan use it, since
/// the reference mesh ([`ModelBody::mesh`]) triangulates the face differently and the two
/// would fight at equal depth. `None` when the body has no plan.
pub fn plan_mesh(&self) -> Option<Arc<Mesh>> {
self.plan_mesh.get_or_init(|| self.display_plan().and_then(|plan| self.body.display_mesh_of(&plan).ok()).map(Arc::new)).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)