forked from 1b-agent/fieldcraft-upstream
render: quadratic elements draw through their mid-side nodes; geometry edges over the mesh follow it
Zoomed in on the hex20 crank arm, the element edges and the shaded faces did not meet (#260). The mesh is conforming; the display drew each hex20 face and edge as straight chords between corners while the mid-side nodes lie on the curved surface (up to 0.44 mm off the chord at 3 mm), and drew the geometry's face boundaries from the design tessellation, off both. Now a quadratic face is cut into triangles whose vertices are its corners and mid-side nodes, each edge is drawn as its two halves, and over the mesh the geometry's edges (and a hovered design edge) are drawn as the element edges on geometry edges (`DisplayEdge::feature`), so faces, edges, feature lines and the highlight share their vertices. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
1 parent
9e38fe866d
commit
a0f48c7207
4 files changed
+233
-78
No files matched your search
@@ -2,8 +2,9 @@
|
||||
//! colours. The GPU viewport and the headless render (`ui.render`) draw the same [`Scene`].
|
||||
//!
|
||||
//! Geometry (the open design, imported surfaces) draws as faces with their boundaries while
|
||||
//! the model has no element faces; once it is meshed, as boundaries only, so the mesh is not
|
||||
//! hidden behind coincident faces. The analysis setup's symbols ([`symbols`]) go over the model,
|
||||
//! the model has no element faces; once it is meshed, as its boundaries drawn along the mesh
|
||||
//! (the element edges on geometry edges), so the mesh is not hidden behind coincident faces and
|
||||
//! every line lies on the element faces. The analysis setup's symbols ([`symbols`]) go over the model,
|
||||
//! muted over a results plot.
|
||||
|
||||
use fieldcraft_render::glyphs::{GlyphShow, Glyphs};
|
||||
@@ -97,14 +98,16 @@ pub fn build(display: &DisplayMesh, style: Style, show: Show, t: &Tokens) -> Sce
|
||||
let color = rgb(if style == Style::Wireframe { t.hex } else { t.mesh_edge });
|
||||
s.lines = display.edges.iter().map(|e| SceneLine { a: e.p[0], b: e.p[1], color, width: EDGE_WIDTH }).collect();
|
||||
}
|
||||
// Face boundaries in every style: they are the geometry's outline.
|
||||
// Face boundaries in every style: they are the geometry's outline. Over the mesh they are
|
||||
// the element edges on geometry edges, so they lie exactly on the element faces; the
|
||||
// geometry's own tessellation lies off them by its chord error.
|
||||
if show.geometry {
|
||||
s.lines.extend(display.surface_edges.iter().map(|[a, b]| SceneLine {
|
||||
a: *a,
|
||||
b: *b,
|
||||
color: rgb(t.geometry_edge),
|
||||
width: GEOMETRY_EDGE_WIDTH,
|
||||
}));
|
||||
let color = rgb(t.geometry_edge);
|
||||
if mesh_faces {
|
||||
s.lines.extend(display.edges.iter().filter(|e| e.feature).map(|e| SceneLine { a: e.p[0], b: e.p[1], color, width: GEOMETRY_EDGE_WIDTH }));
|
||||
} else {
|
||||
s.lines.extend(display.surface_edges.iter().map(|[a, b]| SceneLine { a: *a, b: *b, color, width: GEOMETRY_EDGE_WIDTH }));
|
||||
}
|
||||
}
|
||||
if show.mesh {
|
||||
s.lines.extend(display.lines.iter().map(|l| SceneLine { a: l.a, b: l.b, color: rgb(t.element(l.kind)), width: LINE_ELEMENT_WIDTH }));
|
||||
@@ -181,6 +184,32 @@ mod tests {
|
||||
assert!(s.points.iter().all(|p| p.p.y == 0.0), "only the seeded edge has ticks");
|
||||
}
|
||||
|
||||
/// Over the mesh the geometry's outline is drawn along the element edges on geometry edges,
|
||||
/// never the design's tessellation, which lies off the element faces (#260).
|
||||
#[test]
|
||||
fn geometry_edges_over_the_mesh_follow_its_element_edges() {
|
||||
let mut m = Model::default();
|
||||
for (i, p) in
|
||||
[[0., 0., 0.], [1., 0., 0.], [1., 1., 0.], [0., 1., 0.], [0., 0., 1.], [1., 0., 1.], [1., 1., 1.], [0., 1., 1.]].into_iter().enumerate()
|
||||
{
|
||||
m.set_node(i as u32 + 1, p).unwrap();
|
||||
}
|
||||
m.set_element(1, ElementKind::Hex8, (1..=8).collect()).unwrap();
|
||||
let edge = fieldcraft_model::GeometryRef::new("B", fieldcraft_model::Entity::Edge("e".into())).unwrap();
|
||||
let tag = m.geometry_tag(&edge).unwrap();
|
||||
m.tag_element_edge(1, 1, tag).unwrap();
|
||||
let mut d = DisplayMesh::from_model(&m);
|
||||
// The design's own edge, a little off the element edge (its chord error).
|
||||
d.add_surface(&[[0.0, -0.01, 0.0], [1.0, -0.01, 0.0], [0.5, -0.01, 1.0]], &[[0, 1, 2]], &[0]);
|
||||
let t = Tokens::dark();
|
||||
let geometry = |s: &Scene| s.lines.iter().filter(|l| l.color == rgb(t.geometry_edge)).map(|l| [l.a, l.b]).collect::<Vec<_>>();
|
||||
let s = build(&d, Style::ShadedEdges, Show::DEFAULT, &t);
|
||||
assert_eq!(geometry(&s), vec![[Vec3::new(0.0, 0.0, 0.0), Vec3::new(1.0, 0.0, 0.0)]]);
|
||||
// Without the mesh the design's edges are drawn.
|
||||
let s = build(&d, Style::ShadedEdges, Show { mesh: false, ..Show::DEFAULT }, &t);
|
||||
assert_eq!(geometry(&s).len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn styles_choose_faces_edges_and_nodes() {
|
||||
let mut m = Model::default();
|
||||
|
||||
@@ -389,6 +389,11 @@ impl Picker<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Element faces are drawn: the geometry is drawn along the mesh.
|
||||
fn elements_drawn(&self) -> bool {
|
||||
self.elements && !self.display.tris.is_empty()
|
||||
}
|
||||
|
||||
/// The drawn faces' triangles (for a depth map).
|
||||
fn drawn(&self) -> impl Iterator<Item = [Vec3; 3]> + '_ {
|
||||
let e = self.display.tris.iter().filter(|_| self.elements).map(|t| t.p);
|
||||
@@ -519,7 +524,7 @@ impl Picker<'_> {
|
||||
.display
|
||||
.edges
|
||||
.iter()
|
||||
.filter_map(|e| Some((e.p[0], e.p[1], *self.index.edge_tag.get(&edge_key(e.nodes[0], e.nodes[1]))?)))
|
||||
.filter_map(|e| Some((e.p[0], e.p[1], *self.index.edge_tag.get(&edge_key(e.corners[0], e.corners[1]))?)))
|
||||
.collect();
|
||||
tagged.extend(self.display.lines.iter().zip(&self.index.line_tag).filter_map(|(l, t)| Some((l.a, l.b, (*t)?))));
|
||||
let near = tagged.iter().enumerate().filter_map(|(i, (a, b, _))| {
|
||||
@@ -658,7 +663,9 @@ impl Picker<'_> {
|
||||
}
|
||||
}
|
||||
outline(s, first, color);
|
||||
if m.edges && self.curves {
|
||||
// Over element faces a design edge is marked along the mesh's edges on it (below), which
|
||||
// lie on the faces; its own polyline lies off them by the tessellation's chord error.
|
||||
if m.edges && self.curves && !self.elements_drawn() {
|
||||
// Which edges of each drawn body are marked, worked out once per edge.
|
||||
let marked: Vec<Vec<bool>> = self
|
||||
.names
|
||||
@@ -677,7 +684,7 @@ impl Picker<'_> {
|
||||
}
|
||||
if m.edges {
|
||||
for e in &self.display.edges {
|
||||
if on(self.index.edge_tag.get(&edge_key(e.nodes[0], e.nodes[1])).copied()) {
|
||||
if on(self.index.edge_tag.get(&edge_key(e.corners[0], e.corners[1])).copied()) {
|
||||
s.lines.push(SceneLine { a: e.p[0], b: e.p[1], color, width: EDGE_WIDTH });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user