Skip to main content

fast_mesh_scene/
normals.rs

1//! Smooth vertex normals, kept up to date incrementally from change sets.
2
3use fast_mesh_store::{ChangeSet, Mesh, StoreError, TriangleId, VertexId};
4
5use crate::scene::Entry;
6use crate::{MeshId, Result, Scene, SceneError};
7
8fn oom() -> SceneError {
9    SceneError::Store(StoreError::OutOfMemory)
10}
11
12/// Grows `v` to `len` items of `fill` (or truncates it), failing instead of
13/// aborting when memory runs out.
14fn try_resize<T: Copy>(v: &mut Vec<T>, len: usize, fill: T) -> Result<()> {
15    v.try_reserve(len.saturating_sub(v.len()))
16        .map_err(|_| oom())?;
17    v.resize(len, fill);
18    Ok(())
19}
20
21/// The un-normalised normal of triangle `tri`: twice its area long, so
22/// summing them weights by area. Zero for a triangle repeating a vertex.
23fn face_normal(p: &[[f32; 3]], tri: [VertexId; 3]) -> [f32; 3] {
24    let [a, b, c] = tri.map(|v| p[v as usize]);
25    let e1 = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
26    let e2 = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
27    [
28        e1[1] * e2[2] - e1[2] * e2[1],
29        e1[2] * e2[0] - e1[0] * e2[2],
30        e1[0] * e2[1] - e1[1] * e2[0],
31    ]
32}
33
34/// Appends `vs` to `dirty`, failing instead of aborting when memory runs out.
35fn push(dirty: &mut Vec<VertexId>, vs: &[VertexId]) -> Result<()> {
36    dirty.try_reserve(vs.len()).map_err(|_| oom())?;
37    dirty.extend_from_slice(vs);
38    Ok(())
39}
40
41fn add(sum: &mut [f32; 3], n: [f32; 3]) {
42    for k in 0..3 {
43        sum[k] += n[k];
44    }
45}
46
47/// `n` scaled to unit length; zero stays zero.
48fn normalized(n: [f32; 3]) -> [f32; 3] {
49    let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
50    if len > 0.0 { n.map(|x| x / len) } else { n }
51}
52
53/// A mesh's vertex normals, one per vertex slot, with the changes since
54/// they were last computed.
55pub(crate) struct NormalState {
56    normals: Vec<[f32; 3]>,
57    /// The triangles as they were when the normals were last computed, so
58    /// that the old vertices of a removed or rewritten triangle are known.
59    /// Entries of slots that were dead then are meaningless.
60    triangles: Vec<[VertexId; 3]>,
61    /// Changes since the last computation; `None` means "everything".
62    pending: Option<ChangeSet>,
63}
64
65impl NormalState {
66    /// The normals of `mesh`, computed in full.
67    pub(crate) fn new(mesh: &Mesh) -> Result<NormalState> {
68        let mut state = NormalState {
69            normals: Vec::new(),
70            triangles: Vec::new(),
71            pending: None,
72        };
73        state.compute_all(mesh)?;
74        Ok(state)
75    }
76
77    pub(crate) fn normals(&self) -> &[[f32; 3]] {
78        &self.normals
79    }
80
81    /// Folds a commit's (or undo's or redo's) changes into the pending
82    /// changes, and grows the normals to cover new vertex slots (zero until
83    /// computed). If memory runs out while growing, everything becomes
84    /// pending and the normals stay short until the next update succeeds.
85    pub(crate) fn record(&mut self, changes: &ChangeSet, vertex_slots: usize) {
86        if let Some(p) = &mut self.pending {
87            p.merge(changes);
88        }
89        if vertex_slots > self.normals.len()
90            && try_resize(&mut self.normals, vertex_slots, [0.0; 3]).is_err()
91        {
92            self.pending = None;
93        }
94    }
95
96    /// Marks every normal as stale (after a compaction or a failed
97    /// recovery).
98    pub(crate) fn mark_all(&mut self) {
99        self.pending = None;
100    }
101
102    /// Recomputes every normal from scratch: the sum of the face normals of
103    /// the alive triangles, in ascending triangle order, normalised. This
104    /// matches `fast_mesh::compute_vertex_normals` over the alive triangles.
105    /// It runs [`NormalBuild`] to the end; on error the state is unchanged.
106    fn compute_all(&mut self, mesh: &Mesh) -> Result<()> {
107        let mut build = NormalBuild::new();
108        while !build.step(mesh, usize::MAX)? {}
109        *self = build.finish().ok_or_else(oom)?;
110        Ok(())
111    }
112
113    /// Recomputes the normals the pending changes `c` may have changed and
114    /// returns their vertex ids, ascending without duplicates: every vertex
115    /// of every triangle added, removed or rewritten (old and new
116    /// vertices), and every vertex of every triangle using a moved vertex.
117    /// Added, removed and moved vertices are included too. Past half the
118    /// vertex slots dirty, the collection alone (about 18 ids per moved
119    /// vertex) costs more than a full recomputation, so it recomputes
120    /// everything instead and returns `None`. The same holds past half the
121    /// triangle slots added, removed or rewritten.
122    fn compute_changed(&mut self, mesh: &mut Mesh, c: &ChangeSet) -> Result<Option<Vec<VertexId>>> {
123        let verts = c.vertices_moved.len() + c.vertices_added.len() + c.vertices_removed.len();
124        let tris = c.triangles_added.len() + c.triangles_removed.len() + c.triangles_changed.len();
125        if verts > mesh.vertex_slots() / 2 || tris > mesh.triangle_slots() / 2 {
126            self.compute_all(mesh)?;
127            return Ok(None);
128        }
129        // Every allocation comes first, so a failure leaves the state as it was.
130        mesh.ensure_incidence()?;
131        let (slots, tri_slots) = (mesh.vertex_slots(), mesh.triangle_slots());
132        try_resize(&mut self.normals, slots, [0.0; 3])?;
133        try_resize(&mut self.triangles, tri_slots, [0; 3])?;
134        let mesh = &*mesh;
135        let inc = mesh.incidence().expect("built above");
136        let tris = mesh.triangles();
137        let mut dirty: Vec<VertexId> = Vec::new();
138        for &t in c.triangles_removed.iter().chain(&c.triangles_changed) {
139            if let Some(old) = self.triangles.get(t as usize) {
140                push(&mut dirty, old)?;
141            }
142        }
143        for &t in c.triangles_added.iter().chain(&c.triangles_changed) {
144            if let Some(tri) = mesh.triangle(t) {
145                push(&mut dirty, &tri)?;
146            }
147        }
148        push(&mut dirty, &c.vertices_added)?;
149        push(&mut dirty, &c.vertices_removed)?;
150        push(&mut dirty, &c.vertices_moved)?;
151        for &v in &c.vertices_moved {
152            for &t in inc.triangles(v) {
153                push(&mut dirty, &tris[t as usize])?;
154            }
155        }
156        dirty.sort_unstable();
157        dirty.dedup();
158        dirty.retain(|&v| (v as usize) < slots);
159        // Ascending triangle order per vertex, as in `compute_all`, so the
160        // result matches a full recomputation.
161        let p = mesh.positions();
162        for &v in &dirty {
163            let mut sum = [0.0f32; 3];
164            for &t in inc.triangles(v) {
165                let tri = tris[t as usize];
166                let n = face_normal(p, tri);
167                // A triangle using `v` twice counts twice, as in `compute_all`
168                // (its face normal is zero anyway).
169                for &w in &tri {
170                    if w == v {
171                        add(&mut sum, n);
172                    }
173                }
174            }
175            self.normals[v as usize] = normalized(sum);
176        }
177        for &t in c
178            .triangles_added
179            .iter()
180            .chain(&c.triangles_removed)
181            .chain(&c.triangles_changed)
182        {
183            if let Some(slot) = self.triangles.get_mut(t as usize) {
184                *slot = tris[t as usize];
185            }
186        }
187        self.pending = Some(ChangeSet::default());
188        Ok(Some(dirty))
189    }
190}
191
192/// Vertex slots zeroed, or normalised, per unit of a [`NormalBuild::step`]
193/// budget.
194const VERTICES_PER_UNIT: usize = 16;
195
196/// Triangle slots summed per unit of a [`NormalBuild::step`] budget.
197const TRIANGLES_PER_UNIT: usize = 4;
198
199/// Where a [`NormalBuild`] stands.
200#[derive(Debug, Clone, Copy, PartialEq, Eq)]
201enum NormalStage {
202    /// Zeroing the normals, then copying the triangles, from this slot on.
203    Zero(usize),
204    /// Summing the face normals, from this triangle slot on.
205    Sum(usize),
206    /// Normalising, from this vertex slot on.
207    Normalize(usize),
208    Done,
209}
210
211/// [`NormalState::new`] cut into steps, so that a caller can yield between
212/// them (a mesh added in the background computes its normals this way,
213/// outside the scene). The normals are the same, bit for bit: the face
214/// normals are summed in ascending triangle order either way. The build is
215/// tied to the mesh it is stepped on, which must not change meanwhile.
216#[derive(Debug)]
217pub(crate) struct NormalBuild {
218    normals: Vec<[f32; 3]>,
219    triangles: Vec<[VertexId; 3]>,
220    stage: NormalStage,
221}
222
223impl NormalBuild {
224    pub(crate) fn new() -> NormalBuild {
225        NormalBuild {
226            normals: Vec::new(),
227            triangles: Vec::new(),
228            stage: NormalStage::Zero(0),
229        }
230    }
231
232    pub(crate) fn is_done(&self) -> bool {
233        self.stage == NormalStage::Done
234    }
235
236    /// The normals so far (final once done).
237    pub(crate) fn normals(&self) -> &[[f32; 3]] {
238        &self.normals
239    }
240
241    /// Advances by about `budget` units (at least one): the normals are
242    /// zeroed and the triangles copied (16 vertex slots or 4 triangle slots
243    /// a unit), the face normals of the alive triangles summed (4 triangle
244    /// slots a unit), then every normal normalised (16 a unit). Returns
245    /// whether the normals are done.
246    ///
247    /// # Errors
248    /// Out of memory (both arrays are reserved at full size first); the
249    /// build is unusable after it.
250    pub(crate) fn step(&mut self, mesh: &Mesh, budget: usize) -> Result<bool> {
251        let mut left = budget.max(1);
252        let (slots, tri_slots) = (mesh.vertex_slots(), mesh.triangle_slots());
253        // `n` units of work at `per` items each, from `from` to at most `end`.
254        let take = |left: &mut usize, from: usize, end: usize, per: usize| {
255            let to = end.min(from.saturating_add(left.saturating_mul(per)));
256            *left = left.saturating_sub((to - from).div_ceil(per));
257            to
258        };
259        loop {
260            match self.stage {
261                NormalStage::Zero(from) => {
262                    if from == 0 {
263                        self.normals.try_reserve_exact(slots).map_err(|_| oom())?;
264                        self.triangles
265                            .try_reserve_exact(tri_slots)
266                            .map_err(|_| oom())?;
267                    }
268                    // Zero the normals first, then copy the triangles: one
269                    // cursor over both, the triangles past `slots`.
270                    let end = slots + tri_slots;
271                    let to = if from < slots {
272                        take(&mut left, from, slots, VERTICES_PER_UNIT)
273                    } else {
274                        take(&mut left, from, end, TRIANGLES_PER_UNIT)
275                    };
276                    if from < slots {
277                        self.normals.resize(to, [0.0; 3]);
278                    } else {
279                        self.triangles
280                            .extend_from_slice(&mesh.triangles()[from - slots..to - slots]);
281                    }
282                    self.stage = if to == end {
283                        NormalStage::Sum(0)
284                    } else {
285                        NormalStage::Zero(to)
286                    };
287                }
288                NormalStage::Sum(from) => {
289                    let to = take(&mut left, from, tri_slots, TRIANGLES_PER_UNIT);
290                    let p = mesh.positions();
291                    for (t, &tri) in self.triangles.iter().enumerate().take(to).skip(from) {
292                        if mesh.is_triangle_alive(t as TriangleId) {
293                            let n = face_normal(p, tri);
294                            for v in tri {
295                                add(&mut self.normals[v as usize], n);
296                            }
297                        }
298                    }
299                    self.stage = if to == tri_slots {
300                        NormalStage::Normalize(0)
301                    } else {
302                        NormalStage::Sum(to)
303                    };
304                }
305                NormalStage::Normalize(from) => {
306                    let to = take(&mut left, from, slots, VERTICES_PER_UNIT);
307                    for n in &mut self.normals[from..to] {
308                        *n = normalized(*n);
309                    }
310                    self.stage = if to == slots {
311                        NormalStage::Done
312                    } else {
313                        NormalStage::Normalize(to)
314                    };
315                }
316                NormalStage::Done => return Ok(true),
317            }
318            if left == 0 {
319                return Ok(self.is_done());
320            }
321        }
322    }
323
324    /// The normals once done, as [`NormalState::new`] computes them, with
325    /// nothing pending; `None` before.
326    pub(crate) fn finish(self) -> Option<NormalState> {
327        self.is_done().then_some(NormalState {
328            normals: self.normals,
329            triangles: self.triangles,
330            pending: Some(ChangeSet::default()),
331        })
332    }
333}
334
335impl Entry {
336    /// Reports vertices with new normals to every subscriber (`None`: all).
337    pub(crate) fn report_normals(&mut self, ids: Option<&[VertexId]>) {
338        self.content += 1;
339        for r in &mut self.reports {
340            r.normals_changed(ids);
341        }
342    }
343
344    /// Brings the normals up to date with their pending changes and reports
345    /// the recomputed vertices. Does nothing when normals are off. On error
346    /// the pending changes are kept, so a later call can retry.
347    pub(crate) fn update_normals(&mut self) -> Result<()> {
348        let Some(ns) = &mut self.normals else {
349            return Ok(());
350        };
351        match ns.pending.take() {
352            None => {
353                if let Err(e) = ns.compute_all(&self.mesh) {
354                    ns.pending = None;
355                    return Err(e);
356                }
357                self.report_normals(None);
358            }
359            Some(c) if c.is_empty() && ns.normals.len() == self.mesh.vertex_slots() => {
360                ns.pending = Some(c);
361            }
362            Some(c) => match ns.compute_changed(&mut self.mesh, &c) {
363                Ok(dirty) => self.report_normals(dirty.as_deref()),
364                Err(e) => {
365                    ns.pending = Some(c);
366                    return Err(e);
367                }
368            },
369        }
370        Ok(())
371    }
372}
373
374impl Scene {
375    /// Turns the mesh's vertex normals on or off (off by default). Turning
376    /// them on computes them right away, whatever the mesh's
377    /// [`UpdateMode`](crate::UpdateMode); either switch is reported as
378    /// `normals_dirty: None`. Setting the current state again does nothing.
379    ///
380    /// While on, the normals follow the mesh like its BVH: an Eager mesh's
381    /// right after every edit, undo, redo and cancel, a Lazy one's on
382    /// [`Scene::flush`] or [`Scene::update`], a Manual one's on
383    /// [`Scene::update`] only. A compaction recomputes them in full right
384    /// away. Placement changes leave them alone: they are mesh-local.
385    ///
386    /// Memory: besides 12 bytes of normal per vertex slot, a snapshot of the
387    /// triangles (12 bytes per triangle slot) is kept while normals are on,
388    /// so the old vertices of removed and rewritten triangles are known.
389    ///
390    /// # Errors
391    /// [`SceneError::UnknownMesh`], or [`SceneError::Store`] when there is
392    /// no memory for them. The scene is unchanged on error.
393    pub fn set_normals(&mut self, id: MeshId, on: bool) -> Result<()> {
394        self.drop_field_unit();
395        let entry = self.entries.get_mut(id).ok_or(SceneError::UnknownMesh)?;
396        if on == entry.normals.is_some() {
397            return Ok(());
398        }
399        entry.normals = if on {
400            Some(NormalState::new(&entry.mesh)?)
401        } else {
402            None
403        };
404        entry.report_normals(None);
405        Ok(())
406    }
407
408    /// The mesh's vertex normals in mesh-local space, one per vertex slot,
409    /// or `None` when normals are off. A normal is the normalised sum of the
410    /// area-weighted normals of the alive triangles using the vertex; a
411    /// vertex used by none (or only by degenerate ones), dead slots
412    /// included, reads `[0, 0, 0]`.
413    ///
414    /// The normals of a Lazy or Manual mesh are those of its last update
415    /// (new vertex slots read zero until then). The slice may differ in
416    /// length from `mesh.vertex_slots()` until the next successful update:
417    /// shorter if memory ran out while following an edit, or longer if the
418    /// recomputation after a compaction failed.
419    ///
420    /// # Errors
421    /// [`SceneError::UnknownMesh`] for a stale id.
422    pub fn normals(&self, id: MeshId) -> Result<Option<&[[f32; 3]]>> {
423        Ok(self.entry(id)?.normals.as_ref().map(NormalState::normals))
424    }
425
426    /// A number that goes up (and never down) whenever the mesh's
427    /// positions, triangles or normals change: every commit, undo, redo and
428    /// cancel, compaction, recovery and normals update or switch. Unlike
429    /// [`Mesh::revision`](fast_mesh_store::Mesh::revision), which an undo
430    /// takes back, equal stamps mean nothing in those buffers changed in
431    /// between. Placements and colourings leave it alone.
432    ///
433    /// # Errors
434    /// [`SceneError::UnknownMesh`] for a stale id.
435    #[doc(hidden)]
436    pub fn content_stamp(&self, id: MeshId) -> Result<u64> {
437        Ok(self.entry(id)?.content)
438    }
439}
440
441#[cfg(test)]
442mod tests {
443    use fast_mesh_store::{Mesh, TriangleId, VertexId};
444
445    use crate::{MeshId, MeshOptions, Scene, SceneError, SceneOptions, UpdateMode};
446
447    /// An `n` x `n` grid of unit squares with a wave in z, so that normals
448    /// differ from vertex to vertex.
449    fn wavy(n: u32) -> Mesh {
450        let mut pos = Vec::new();
451        for y in 0..=n {
452            for x in 0..=n {
453                let (fx, fy) = (x as f32, y as f32);
454                pos.extend_from_slice(&[fx, fy, 0.3 * (fx * 0.9).sin() * (fy * 0.7).cos()]);
455            }
456        }
457        let mut idx = Vec::new();
458        for y in 0..n {
459            for x in 0..n {
460                let a = y * (n + 1) + x;
461                let (b, c) = (a + 1, a + n + 1);
462                idx.extend_from_slice(&[a, b, c, b, c + 1, c]);
463            }
464        }
465        Mesh::from_buffers(&pos, &idx).unwrap()
466    }
467
468    fn scene_with(mode: UpdateMode) -> (Scene, MeshId) {
469        let mut scene = Scene::new(SceneOptions::default());
470        let options = MeshOptions {
471            mode,
472            ..MeshOptions::default()
473        };
474        let id = scene.add_mesh(wavy(6), options).unwrap();
475        (scene, id)
476    }
477
478    /// `fast_mesh::compute_vertex_normals` over the alive triangles.
479    fn reference(mesh: &Mesh) -> Vec<[f32; 3]> {
480        let flat: Vec<f32> = mesh.positions().iter().flatten().copied().collect();
481        let idx: Vec<u32> = (0..mesh.triangle_slots() as TriangleId)
482            .filter_map(|t| mesh.triangle(t))
483            .flatten()
484            .collect();
485        let n = fast_mesh::compute_vertex_normals(&flat, &idx).unwrap();
486        n.chunks(3).map(|c| [c[0], c[1], c[2]]).collect()
487    }
488
489    fn assert_matches(scene: &Scene, id: MeshId) {
490        let mesh = scene.mesh(id).unwrap();
491        let got = scene.normals(id).unwrap().expect("normals are on");
492        let want = reference(mesh);
493        assert_eq!(got.len(), mesh.vertex_slots());
494        for (v, (g, w)) in got.iter().zip(&want).enumerate() {
495            for k in 0..3 {
496                assert!((g[k] - w[k]).abs() <= 1e-6, "vertex {v}: {g:?} vs {w:?}");
497            }
498        }
499    }
500
501    fn normals_dirty(scene: &mut Scene, id: MeshId) -> Option<Vec<VertexId>> {
502        scene
503            .take_changes()
504            .into_iter()
505            .find(|c| c.id == id)
506            .map_or(Some(Vec::new()), |c| c.normals_dirty)
507    }
508
509    fn move_by(scene: &mut Scene, id: MeshId, v: VertexId, d: [f32; 3]) {
510        scene
511            .edit(id, |tx| {
512                let p = tx.mesh().position(v).unwrap();
513                tx.set_positions(&[v], &[std::array::from_fn(|k| p[k] + d[k])])
514            })
515            .unwrap();
516    }
517
518    /// Every vertex of every alive triangle using `v`, ascending.
519    fn ring_of_ring(mesh: &Mesh, v: VertexId) -> Vec<VertexId> {
520        let mut out: Vec<VertexId> = (0..mesh.triangle_slots() as TriangleId)
521            .filter_map(|t| mesh.triangle(t))
522            .filter(|tri| tri.contains(&v))
523            .flatten()
524            .collect();
525        out.sort_unstable();
526        out.dedup();
527        out
528    }
529
530    #[test]
531    fn normals_match_compute_vertex_normals_on_add() {
532        let (mut scene, id) = scene_with(UpdateMode::Eager);
533        scene.set_normals(id, true).unwrap();
534        assert_matches(&scene, id);
535        let n = scene.normals(id).unwrap().unwrap();
536        assert!(n.iter().all(|n| n[2] > 0.0), "upward wound grid");
537    }
538
539    #[test]
540    fn normals_follow_vertex_move() {
541        let (mut scene, id) = scene_with(UpdateMode::Eager);
542        scene.set_normals(id, true).unwrap();
543        scene.take_changes();
544        let v = 3 * 7 + 3;
545        move_by(&mut scene, id, v, [0.1, -0.2, 0.7]);
546        let want = ring_of_ring(scene.mesh(id).unwrap(), v);
547        assert!(want.len() > 1 && want.len() < 20, "{want:?}");
548        assert_eq!(normals_dirty(&mut scene, id), Some(want));
549        assert_matches(&scene, id);
550        // An undo reports the same set and restores the values.
551        scene.undo().unwrap();
552        let want = ring_of_ring(scene.mesh(id).unwrap(), v);
553        assert_eq!(normals_dirty(&mut scene, id), Some(want));
554        assert_matches(&scene, id);
555        // Topology edits report the old and new triangles' vertices.
556        let mesh = scene.mesh(id).unwrap();
557        let old = mesh.triangle(10).unwrap();
558        scene.edit(id, |tx| tx.remove_triangles(&[10])).unwrap();
559        let mut want = old.to_vec();
560        want.sort_unstable();
561        assert_eq!(normals_dirty(&mut scene, id), Some(want));
562        assert_matches(&scene, id);
563        let mesh = scene.mesh(id).unwrap();
564        let t = (0..mesh.triangle_slots() as TriangleId)
565            .find(|&t| mesh.is_triangle_alive(t))
566            .unwrap();
567        let [a, b, c] = mesh.triangle(t).unwrap();
568        let far = 6 * 7 + 6;
569        scene
570            .edit(id, |tx| tx.set_triangles(&[t], &[[a, b, far]]))
571            .unwrap();
572        let mut want = vec![a, b, c, far];
573        want.sort_unstable();
574        want.dedup();
575        assert_eq!(normals_dirty(&mut scene, id), Some(want));
576        assert_matches(&scene, id);
577        // A placement leaves normals alone.
578        let mut p = scene.placement(id).unwrap();
579        p.translation = [5.0, 0.0, 0.0];
580        scene.set_placement(id, p).unwrap();
581        assert_eq!(normals_dirty(&mut scene, id), Some(Vec::new()));
582    }
583
584    #[test]
585    fn normal_build_in_steps_matches_compute_all() {
586        // Dead triangles and a vertex no triangle uses: their slots are
587        // skipped and stay zero, as in one call.
588        let (mut scene, id) = scene_with(UpdateMode::Eager);
589        scene
590            .edit(id, |tx| {
591                tx.remove_triangles(&[0, 1, 7, 30])?;
592                tx.add_vertices(&[[9.0, 9.0, 9.0]])
593            })
594            .unwrap();
595        let mesh = scene.mesh(id).unwrap();
596        let want = super::NormalState::new(mesh).unwrap();
597        let bits =
598            |n: &[[f32; 3]]| -> Vec<u32> { n.iter().flatten().map(|x| x.to_bits()).collect() };
599        for budget in [1, 2, 7, 100, usize::MAX] {
600            let mut build = super::NormalBuild::new();
601            let mut steps = 0;
602            while !build.step(mesh, budget).unwrap() {
603                steps += 1;
604            }
605            if budget == 1 {
606                assert!(steps > mesh.triangle_slots() / 8, "{steps}");
607            }
608            assert!(build.step(mesh, budget).unwrap(), "stays done");
609            let got = build.finish().unwrap();
610            assert_eq!(bits(got.normals()), bits(want.normals()), "budget {budget}");
611            assert_eq!(got.triangles, want.triangles);
612            assert_eq!(got.pending, Some(fast_mesh_store::ChangeSet::default()));
613        }
614        // Unfinished, it gives nothing.
615        let mut build = super::NormalBuild::new();
616        build.step(mesh, 1).unwrap();
617        assert!(build.finish().is_none());
618    }
619
620    #[test]
621    fn content_stamp_moves_on_every_change_of_the_render_buffers() {
622        // It only goes up: on an edit, an undo (which takes the store's
623        // revision back), a redo, a compaction and a normals switch; never
624        // on a placement or a colouring.
625        let (mut scene, id) = scene_with(UpdateMode::Eager);
626        let mut last = scene.content_stamp(id).unwrap();
627        let mut moved = |scene: &Scene, what: &str, want: bool| {
628            let now = scene.content_stamp(id).unwrap();
629            assert!(now >= last, "{what}: went down");
630            assert_eq!(now > last, want, "{what}");
631            last = now;
632        };
633        scene.set_normals(id, true).unwrap();
634        moved(&scene, "normals on", true);
635        move_by(&mut scene, id, 5, [0.0, 0.0, 0.5]);
636        moved(&scene, "edit", true);
637        scene.undo().unwrap();
638        moved(&scene, "undo", true);
639        scene.redo().unwrap();
640        moved(&scene, "redo", true);
641        scene.edit(id, |tx| tx.remove_triangles(&[3])).unwrap();
642        scene.compact(id).unwrap();
643        moved(&scene, "remove and compact", true);
644        let mut p = scene.placement(id).unwrap();
645        p.translation = [2.0, 0.0, 0.0];
646        scene.set_placement(id, p).unwrap();
647        moved(&scene, "placement", false);
648        scene.set_normals(id, false).unwrap();
649        moved(&scene, "normals off", true);
650        scene.remove_mesh(id).unwrap();
651        assert_eq!(scene.content_stamp(id), Err(SceneError::UnknownMesh));
652    }
653
654    #[test]
655    fn normals_off_by_default_and_none() {
656        let (mut scene, id) = scene_with(UpdateMode::Eager);
657        assert_eq!(scene.normals(id), Ok(None));
658        assert_eq!(normals_dirty(&mut scene, id), None, "added");
659        move_by(&mut scene, id, 5, [0.0, 0.0, 1.0]);
660        assert_eq!(normals_dirty(&mut scene, id), Some(Vec::new()), "off");
661        scene.set_normals(id, true).unwrap();
662        assert_eq!(normals_dirty(&mut scene, id), None);
663        scene.set_normals(id, true).unwrap();
664        assert!(scene.take_changes().is_empty(), "no change");
665        scene.set_normals(id, false).unwrap();
666        assert_eq!(scene.normals(id), Ok(None));
667        assert_eq!(normals_dirty(&mut scene, id), None);
668        scene.remove_mesh(id).unwrap();
669        assert_eq!(scene.set_normals(id, true), Err(SceneError::UnknownMesh));
670        assert_eq!(scene.normals(id), Err(SceneError::UnknownMesh));
671    }
672
673    #[test]
674    fn normals_after_compact_all() {
675        for mode in [UpdateMode::Eager, UpdateMode::Lazy, UpdateMode::Manual] {
676            let (mut scene, id) = scene_with(mode);
677            scene.set_normals(id, true).unwrap();
678            let mesh = scene.mesh(id).unwrap();
679            let doomed: Vec<TriangleId> = (0..mesh.triangle_slots() as TriangleId)
680                .filter(|&t| mesh.triangle(t).unwrap().contains(&0))
681                .collect();
682            scene
683                .edit(id, |tx| {
684                    tx.remove_triangles(&doomed)?;
685                    tx.remove_vertices(&[0])
686                })
687                .unwrap();
688            scene.take_changes();
689            scene.compact(id).unwrap();
690            assert_eq!(scene.mesh(id).unwrap().vertex_slots(), 48);
691            assert_eq!(normals_dirty(&mut scene, id), None, "{mode:?}");
692            assert_matches(&scene, id);
693        }
694    }
695
696    #[test]
697    fn lazy_normals_update_on_flush() {
698        let (mut scene, id) = scene_with(UpdateMode::Lazy);
699        scene.set_normals(id, true).unwrap();
700        scene.take_changes();
701        let before = scene.normals(id).unwrap().unwrap().to_vec();
702        move_by(&mut scene, id, 10, [0.0, 0.0, 0.8]);
703        let added = scene
704            .edit(id, |tx| {
705                let v = tx.add_vertices(&[[0.5, 0.5, 2.0], [9.0, 9.0, 9.0]])?;
706                tx.add_triangles(&[[0, 1, v[0]]])?;
707                Ok::<_, SceneError>(v)
708            })
709            .unwrap();
710        let slots = scene.mesh(id).unwrap().vertex_slots();
711        let now = scene.normals(id).unwrap().unwrap();
712        assert_eq!(now.len(), slots, "grown with the slots");
713        assert_eq!(&now[..before.len()], &before[..], "stale until flushed");
714        assert_eq!(normals_dirty(&mut scene, id), Some(Vec::new()));
715        scene.flush().unwrap();
716        assert_matches(&scene, id);
717        let mesh = scene.mesh(id).unwrap();
718        let mut want = ring_of_ring(mesh, 10);
719        want.extend([0, 1, added[0], added[1]]);
720        want.sort_unstable();
721        want.dedup();
722        assert_eq!(normals_dirty(&mut scene, id), Some(want));
723        assert_eq!(
724            scene.normals(id).unwrap().unwrap()[added[1] as usize],
725            [0.0; 3]
726        );
727
728        // Manual: flush leaves the normals alone, `update` brings them up.
729        scene.set_mode(id, UpdateMode::Manual).unwrap();
730        move_by(&mut scene, id, 20, [0.0, 0.3, 0.0]);
731        scene.flush().unwrap();
732        assert_eq!(normals_dirty(&mut scene, id), Some(Vec::new()));
733        scene.update(id).unwrap();
734        assert_eq!(
735            normals_dirty(&mut scene, id),
736            Some(ring_of_ring(scene.mesh(id).unwrap(), 20))
737        );
738        assert_matches(&scene, id);
739        // Switching to Eager catches up at once.
740        move_by(&mut scene, id, 30, [0.2, 0.0, 0.0]);
741        scene.set_mode(id, UpdateMode::Eager).unwrap();
742        assert_matches(&scene, id);
743    }
744
745    #[test]
746    fn dense_normals_recompute_all_and_report_all() {
747        let (mut scene, id) = scene_with(UpdateMode::Eager);
748        scene.set_normals(id, true).unwrap();
749        scene.take_changes();
750        // Move every vertex: past half dirty, so the normals are recomputed
751        // in full and reported as all, skipping the dirty sort.
752        let mesh = scene.mesh(id).unwrap();
753        let all: Vec<VertexId> = (0..mesh.vertex_slots() as VertexId)
754            .filter(|&v| mesh.is_vertex_alive(v))
755            .collect();
756        assert!(all.len() > 1);
757        scene
758            .edit(id, |tx| {
759                let spots: Vec<[f32; 3]> = all
760                    .iter()
761                    .map(|&v| {
762                        let p = tx.mesh().position(v).unwrap();
763                        [p[0], p[1], p[2] + 1.0]
764                    })
765                    .collect();
766                tx.set_positions(&all, &spots)
767            })
768            .unwrap();
769        assert_eq!(normals_dirty(&mut scene, id), None);
770        assert_matches(&scene, id);
771    }
772
773    #[test]
774    fn dense_triangle_removal_reports_all() {
775        let (mut scene, id) = scene_with(UpdateMode::Eager);
776        scene.set_normals(id, true).unwrap();
777        scene.take_changes();
778        // Remove past half the triangles without moving any vertex: only the
779        // triangle threshold can trip the full recompute.
780        let mesh = scene.mesh(id).unwrap();
781        let doomed: Vec<TriangleId> = (0..mesh.triangle_slots() as TriangleId)
782            .filter(|&t| mesh.is_triangle_alive(t))
783            .take(mesh.triangle_slots() / 2 + 1)
784            .collect();
785        assert!(doomed.len() > 1);
786        scene.edit(id, |tx| tx.remove_triangles(&doomed)).unwrap();
787        assert_eq!(normals_dirty(&mut scene, id), None);
788        assert_matches(&scene, id);
789    }
790}