Skip to main content

mesh_graph/
utils.rs

1#[cfg(feature = "rerun")]
2use std::borrow::Borrow;
3
4#[cfg(any(test, feature = "rerun"))]
5use glam::Vec3;
6#[cfg(feature = "rerun")]
7use glam::{Quat, Vec2};
8
9#[cfg(feature = "rerun")]
10pub fn vec3_array(v: impl Borrow<Vec3>) -> [f32; 3] {
11    [v.borrow().x, v.borrow().y, v.borrow().z]
12}
13
14#[cfg(feature = "rerun")]
15pub fn vec2_array(v: impl Borrow<Vec2>) -> [f32; 3] {
16    [v.borrow().x, v.borrow().y, 0.0]
17}
18
19#[cfg(feature = "rerun")]
20pub fn quat_array(q: impl Borrow<Quat>) -> [f32; 4] {
21    [q.borrow().x, q.borrow().y, q.borrow().z, q.borrow().w]
22}
23
24#[macro_export]
25macro_rules! error_none {
26    ($msg:literal $(, $args:expr)*) => {
27        || {
28            tracing::error!($msg $(, $args)*);
29            None
30        }
31    };
32}
33
34macro_rules! unwrap_or_return {
35    ($code:expr, $error:expr, $ret:expr) => {
36        match $code {
37            Some(value) => value,
38            None => {
39                tracing::error!($error);
40                return $ret;
41            }
42        }
43    };
44    ($code:expr, $error:expr) => {
45        match $code {
46            Some(value) => value,
47            None => {
48                tracing::error!($error);
49                return;
50            }
51        }
52    };
53}
54
55// Shared test helpers. Used by the `ops::collapse` and `ops::subdivide` test
56// modules, which both need a mesh with a known edge-length distribution and a
57// way to assert the half-edge invariants survived an operation.
58#[cfg(test)]
59use crate::{HalfedgeId, MeshGraph, VertexId};
60
61/// Builds an `(n x n)` grid of quads in the XY plane, each quad split into two
62/// triangles (CCW when viewed from `+z`), from scratch with the public add APIs.
63#[cfg(test)]
64pub(crate) fn build_grid(n: usize) -> MeshGraph {
65    let mut g = MeshGraph::new();
66
67    let cell = 1.0;
68    let mut v = vec![vec![]; n + 1];
69    for (j, row) in v.iter_mut().enumerate() {
70        for i in 0..=n {
71            let id = g.add_vertex(Vec3::new(i as f32 * cell, j as f32 * cell, 0.0));
72            row.push(id);
73        }
74    }
75
76    let edge = |g: &mut MeshGraph, a: VertexId, b: VertexId| -> HalfedgeId {
77        g.add_or_get_edge(a, b).unwrap().start_to_end_he_id
78    };
79
80    for j in 0..n {
81        for i in 0..n {
82            let a = v[j][i];
83            let b = v[j][i + 1];
84            let c = v[j + 1][i + 1];
85            let d = v[j + 1][i];
86
87            // triangles a-b-c and a-c-d
88            let he_ab = edge(&mut g, a, b);
89            let he_bc = edge(&mut g, b, c);
90            let he_ca = edge(&mut g, c, a);
91            g.add_face(he_ab, he_bc, he_ca);
92
93            let he_ac = edge(&mut g, a, c);
94            let he_cd = edge(&mut g, c, d);
95            let he_da = edge(&mut g, d, a);
96            g.add_face(he_ac, he_cd, he_da);
97        }
98    }
99
100    g
101}
102
103/// Returns a human-readable list of broken half-edge invariants, empty when the
104/// mesh is well formed: mutual twins, live start/end vertices with positions,
105/// live `face`/`next`, and `outgoing_halfedges` agreeing with the topology.
106#[cfg(test)]
107pub(crate) fn mesh_invariant_violations(mg: &MeshGraph) -> Vec<String> {
108    let mut problems = Vec::new();
109
110    for (he_id, he) in &mg.halfedges {
111        let Some(twin_id) = he.twin else {
112            problems.push(format!("he {he_id:?}: missing twin"));
113            continue;
114        };
115        match mg.halfedges.get(twin_id) {
116            Some(twin) if twin.twin == Some(he_id) => {}
117            _ => problems.push(format!(
118                "he {he_id:?}: twin {twin_id:?} does not point back"
119            )),
120        }
121
122        let Some(sv) = he.start_vertex(mg) else {
123            problems.push(format!("he {he_id:?}: no start vertex"));
124            continue;
125        };
126        if !mg.vertices.contains_key(sv) || !mg.positions.contains_key(sv) {
127            problems.push(format!("he {he_id:?}: start vertex {sv:?} is dead"));
128        }
129        if !mg.vertices.contains_key(he.end_vertex) || !mg.positions.contains_key(he.end_vertex) {
130            problems.push(format!(
131                "he {he_id:?}: end vertex {:?} is dead",
132                he.end_vertex
133            ));
134        }
135        if let Some(f) = he.face
136            && !mg.faces.contains_key(f)
137        {
138            problems.push(format!("he {he_id:?}: face {f:?} is dead"));
139        }
140        if let Some(n) = he.next
141            && !mg.halfedges.contains_key(n)
142        {
143            problems.push(format!("he {he_id:?}: next {n:?} is dead"));
144        }
145
146        // halfedge must be present in its start vertex's outgoing list
147        match mg.outgoing_halfedges.get(sv) {
148            Some(list) if list.contains(&he_id) => {}
149            _ => problems.push(format!("he {he_id:?}: not in outgoing_halfedges[{sv:?}]")),
150        }
151    }
152
153    for (v_id, list) in &mg.outgoing_halfedges {
154        for &he_id in list {
155            let Some(he) = mg.halfedges.get(he_id) else {
156                problems.push(format!("outgoing_halfedges[{v_id:?}]: stale he {he_id:?}"));
157                continue;
158            };
159            if he.start_vertex(mg) != Some(v_id) {
160                problems.push(format!(
161                    "outgoing_halfedges[{v_id:?}]: he {he_id:?} does not start here"
162                ));
163            }
164        }
165    }
166
167    problems
168}
169
170#[cfg(test)]
171pub(crate) fn get_tracing_subscriber() {
172    if let Err(e) = tracing_subscriber::fmt()
173        .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
174        .with_line_number(true)
175        .pretty()
176        .try_init()
177    {
178        tracing::warn!("Tracing subscriber already initialized: {}", e);
179    }
180}
181
182pub(crate) use unwrap_or_return;
183
184#[cfg(test)]
185fn extend_outer_corners(
186    meshgraph: &mut crate::MeshGraph,
187    new_vertex_ids: &mut Vec<crate::VertexId>,
188    outer_vertex_ids: &[crate::VertexId],
189    scalar: f32,
190    steps: usize,
191) {
192    if steps == 0 {
193        return;
194    }
195
196    let mut corner_vertex_ids = Vec::with_capacity(outer_vertex_ids.len());
197
198    // mesh star corners to make the mesh larger
199    for i in 0..outer_vertex_ids.len() {
200        let point_1 = meshgraph.positions.get(outer_vertex_ids[i]).unwrap();
201        let point_2 = meshgraph
202            .positions
203            .get(outer_vertex_ids[(i + 1) % outer_vertex_ids.len()])
204            .unwrap();
205
206        let mut point_3 = point_1
207            + ((point_2 - point_1) * 0.5)
208            + (point_1 + point_2).normalize() * scalar / steps as f32;
209
210        point_3.z = 0.0; // allow expansion only in x-y-plane
211
212        let vertex_id = meshgraph.add_vertex(point_3);
213        corner_vertex_ids.push(vertex_id);
214    }
215
216    for cv_i in 0..corner_vertex_ids.len() {
217        let corner_vertex_id = corner_vertex_ids[cv_i];
218        let vertex_id = outer_vertex_ids[cv_i];
219        let next_vertext_id = outer_vertex_ids[(cv_i + 1) % outer_vertex_ids.len()];
220        let halfedge_vertex_to_corner_id = meshgraph
221            .add_or_get_edge(vertex_id, corner_vertex_id)
222            .unwrap()
223            .start_to_end_he_id;
224        let halfedge_vertex_to_next_vertex_id = meshgraph
225            .add_or_get_edge(vertex_id, next_vertext_id)
226            .unwrap()
227            .start_to_end_he_id;
228
229        meshgraph
230            .add_face_from_halfedges(
231                halfedge_vertex_to_corner_id,
232                halfedge_vertex_to_next_vertex_id,
233            )
234            .unwrap();
235
236        let halfedge_corner_to_next_vertex_id = meshgraph
237            .add_or_get_edge(corner_vertex_id, next_vertext_id)
238            .unwrap()
239            .start_to_end_he_id;
240
241        let halfedge_next_vertex_to_next_corner_vertex_id = meshgraph
242            .add_or_get_edge(
243                next_vertext_id,
244                corner_vertex_ids[(cv_i + 1) % corner_vertex_ids.len()],
245            )
246            .unwrap()
247            .start_to_end_he_id;
248
249        meshgraph
250            .add_face_from_halfedges(
251                halfedge_corner_to_next_vertex_id,
252                halfedge_next_vertex_to_next_corner_vertex_id,
253            )
254            .unwrap();
255    }
256
257    extend_outer_corners(
258        meshgraph,
259        new_vertex_ids,
260        &corner_vertex_ids,
261        scalar,
262        steps - 1,
263    );
264
265    new_vertex_ids.extend(corner_vertex_ids);
266}
267
268/// Extend a mesh graph with new points.
269/// Expects the first point to be the geometrical center of the new vertices.
270/// Mesh then extends further by `steps` iterations from the center outward.
271#[cfg(test)]
272pub(crate) fn extend_with(
273    meshgraph: &mut crate::MeshGraph,
274    center_and_points: &[Vec3],
275    matrix: glam::Mat4,
276    scalar: f32,
277    steps: usize,
278) -> Vec<crate::VertexId> {
279    let (center, points) = center_and_points.split_first().unwrap();
280    let center_id = meshgraph.add_vertex(*center);
281
282    let mut vertex_ids = Vec::new();
283    let mut halfedge_ids = Vec::new();
284
285    for point in points {
286        let vertex_id = meshgraph.add_vertex(*point);
287        let halfedge_id = meshgraph
288            .add_or_get_edge(center_id, vertex_id)
289            .unwrap()
290            .start_to_end_he_id;
291
292        vertex_ids.push(vertex_id);
293        halfedge_ids.push(halfedge_id);
294    }
295
296    for i in 0..points.len() {
297        meshgraph
298            .add_face_from_halfedges(halfedge_ids[i], halfedge_ids[(i + 1) % points.len()])
299            .unwrap();
300    }
301
302    let mut new_vertex_ids = vec![center_id];
303    new_vertex_ids.extend(vertex_ids.clone());
304    extend_outer_corners(meshgraph, &mut new_vertex_ids, &vertex_ids, scalar, steps);
305
306    for new_vertex_id in new_vertex_ids.iter() {
307        if let Some(pos) = meshgraph.positions.get_mut(*new_vertex_id) {
308            *pos = matrix.transform_point3(*pos);
309        };
310    }
311
312    new_vertex_ids
313}