Skip to main content

fast_mesh_store/
mesh.rs

1use std::fmt;
2use std::sync::atomic::{AtomicU64, Ordering};
3
4use crate::adjacency::Adjacency;
5use crate::channel::{Channel, ChannelDesc, ChannelKind, UV_CHANNEL};
6use crate::column::{Column, try_reserve, try_reserve_exact, try_vec};
7use crate::incidence::Incidence;
8use crate::{Patch, PatchSide, PatchState, Result, StoreError, TriangleId, VertexId};
9
10static NEXT_MESH_UID: AtomicU64 = AtomicU64::new(1);
11
12/// An editable triangle mesh stored in slots.
13///
14/// Vertices and triangles are addressed by slot ids that stay stable across edits,
15/// undo and redo. Removed slots are dead: `positions()` and `triangles()` still contain
16/// them (removed triangles read `[0, 0, 0]`, which renderers draw as nothing), and
17/// `is_vertex_alive` / `is_triangle_alive` tell them apart. Only [`Mesh::compact`]
18/// renumbers slots.
19pub struct Mesh {
20    pub(crate) uid: u64,
21    pub(crate) epoch: u32,
22    pub(crate) revision: u64,
23    pub(crate) next_revision: u64,
24    pub(crate) origin: [f64; 3],
25    pub(crate) positions: Column<[f32; 3]>,
26    pub(crate) triangles: Column<[u32; 3]>,
27    pub(crate) vertex_live: Column<u64>,
28    pub(crate) triangle_live: Column<u64>,
29    /// Number of triangle corners referencing each vertex.
30    pub(crate) vertex_refs: Column<u32>,
31    pub(crate) channels: Vec<Channel>,
32    /// Id for the next added channel.
33    pub(crate) next_channel_id: u64,
34    pub(crate) vertex_free: Vec<VertexId>,
35    pub(crate) triangle_free: Vec<TriangleId>,
36    pub(crate) live_vertices: usize,
37    pub(crate) live_triangles: usize,
38    /// Cached corner table; dropped on every topology change.
39    pub(crate) adjacency: Option<Adjacency>,
40    /// Cached vertex → triangles index; follows edits, dropped by `compact`.
41    pub(crate) incidence: Option<Incidence>,
42    /// True inside a transaction that changed topology: `incidence` is stale
43    /// until the commit updates it or the rollback restores the mesh.
44    pub(crate) incidence_pending: bool,
45}
46
47pub(crate) fn bit(bits: &Column<u64>, i: usize) -> bool {
48    let word = i / 64;
49    word < bits.len() && (bits.get(word) >> (i % 64)) & 1 == 1
50}
51
52pub(crate) fn set_bit(bits: &mut Column<u64>, i: usize, on: bool) -> Result<()> {
53    let word = i / 64;
54    bits.grow_to(word + 1, 0)?;
55    let old = bits.get(word);
56    let mask = 1u64 << (i % 64);
57    let new = if on { old | mask } else { old & !mask };
58    if new != old {
59        bits.set(word, new);
60    }
61    Ok(())
62}
63
64pub(crate) fn full_bits(len: usize) -> Result<Vec<u64>> {
65    let mut words = try_vec(len.div_ceil(64), u64::MAX)?;
66    if let Some(last) = words.last_mut()
67        && !len.is_multiple_of(64)
68    {
69        *last = (1u64 << (len % 64)) - 1;
70    }
71    Ok(words)
72}
73
74/// Shows ids and counts, not the (potentially huge) buffers.
75impl fmt::Debug for Mesh {
76    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77        f.debug_struct("Mesh")
78            .field("uid", &self.uid)
79            .field("revision", &self.revision)
80            .field("origin", &self.origin)
81            .field("vertices", &self.live_vertices)
82            .field("vertex_slots", &self.vertex_slots())
83            .field("triangles", &self.live_triangles)
84            .field("triangle_slots", &self.triangle_slots())
85            .field(
86                "channels",
87                &self
88                    .channels
89                    .iter()
90                    .filter(|c| !c.hidden)
91                    .map(|c| c.desc.name.as_str())
92                    .collect::<Vec<_>>(),
93            )
94            .finish_non_exhaustive()
95    }
96}
97
98impl Default for Mesh {
99    fn default() -> Self {
100        Self::new()
101    }
102}
103
104impl Mesh {
105    /// An empty mesh with its origin at zero.
106    pub fn new() -> Self {
107        // Empty liveness bitsets need no allocation, so this cannot fail.
108        Self::assemble(
109            [0.0; 3],
110            Vec::new(),
111            Vec::new(),
112            Vec::new(),
113            Vec::new(),
114            Vec::new(),
115        )
116    }
117
118    /// Builds a mesh from flat `[x, y, z, ...]` positions and `[a, b, c, ...]` indices.
119    pub fn from_buffers(positions: &[f32], indices: &[u32]) -> Result<Self> {
120        if !positions.len().is_multiple_of(3) {
121            return Err(StoreError::InvalidArgument(
122                "positions length must be a multiple of 3",
123            ));
124        }
125        let mut points = try_vec(positions.len() / 3, [0.0f32; 3])?;
126        for (p, c) in points.iter_mut().zip(positions.as_chunks::<3>().0) {
127            *p = *c;
128        }
129        Self::build([0.0; 3], points, indices)
130    }
131
132    /// Builds a mesh from double-precision positions. The origin is set to the center
133    /// of the bounding box and positions are stored as `f32` offsets from it, which
134    /// keeps precision for coordinates far from zero. Every coordinate, the
135    /// computed origin and every offset (as an `f32`) must be finite.
136    pub fn from_buffers_f64(positions: &[f64], indices: &[u32]) -> Result<Self> {
137        if !positions.len().is_multiple_of(3) {
138            return Err(StoreError::InvalidArgument(
139                "positions length must be a multiple of 3",
140            ));
141        }
142        let not_finite = StoreError::InvalidArgument("positions must be finite");
143        if !positions.iter().all(|x| x.is_finite()) {
144            return Err(not_finite);
145        }
146        let mut min = [f64::INFINITY; 3];
147        let mut max = [f64::NEG_INFINITY; 3];
148        for c in positions.as_chunks::<3>().0 {
149            for k in 0..3 {
150                min[k] = min[k].min(c[k]);
151                max[k] = max[k].max(c[k]);
152            }
153        }
154        let origin = if positions.is_empty() {
155            [0.0; 3]
156        } else {
157            [0, 1, 2].map(|k| (min[k] + max[k]) * 0.5)
158        };
159        Self::with_origin_checked(positions, indices, origin)
160    }
161
162    /// Builds a mesh from double-precision positions relative to a given origin:
163    /// positions are stored as `(p - origin)` in `f32`. Every coordinate, the
164    /// origin and every offset (as an `f32`) must be finite.
165    pub fn from_buffers_f64_with_origin(
166        positions: &[f64],
167        indices: &[u32],
168        origin: [f64; 3],
169    ) -> Result<Self> {
170        if !positions.len().is_multiple_of(3) {
171            return Err(StoreError::InvalidArgument(
172                "positions length must be a multiple of 3",
173            ));
174        }
175        if !positions.iter().all(|x| x.is_finite()) {
176            return Err(StoreError::InvalidArgument("positions must be finite"));
177        }
178        Self::with_origin_checked(positions, indices, origin)
179    }
180
181    /// Shared tail of the `f64` constructors; `positions` is already validated.
182    /// Offsets from the origin too large for an `f32` are rejected like
183    /// non-finite input.
184    fn with_origin_checked(positions: &[f64], indices: &[u32], origin: [f64; 3]) -> Result<Self> {
185        let not_finite = StoreError::InvalidArgument("positions must be finite");
186        if !origin.iter().all(|x| x.is_finite()) {
187            return Err(not_finite);
188        }
189        let mut points = try_vec(positions.len() / 3, [0.0f32; 3])?;
190        for (p, c) in points.iter_mut().zip(positions.as_chunks::<3>().0) {
191            *p = [0, 1, 2].map(|k| (c[k] - origin[k]) as f32);
192            if !p.iter().all(|x| x.is_finite()) {
193                return Err(not_finite);
194            }
195        }
196        Self::build(origin, points, indices)
197    }
198
199    fn build(origin: [f64; 3], points: Vec<[f32; 3]>, indices: &[u32]) -> Result<Self> {
200        if !indices.len().is_multiple_of(3) {
201            return Err(StoreError::InvalidArgument(
202                "indices length must be a multiple of 3",
203            ));
204        }
205        let vertex_count = points.len();
206        let mut refs = try_vec(vertex_count, 0u32)?;
207        let mut tris = try_vec(indices.len() / 3, [0u32; 3])?;
208        for (t, c) in tris.iter_mut().zip(indices.as_chunks::<3>().0) {
209            for &v in c {
210                if v as usize >= vertex_count {
211                    return Err(StoreError::InvalidVertex(v));
212                }
213                refs[v as usize] += 1;
214            }
215            *t = *c;
216        }
217        Self::from_parts(origin, points, tris, refs)
218    }
219
220    /// A mesh whose vertices and triangles are all alive.
221    fn from_parts(
222        origin: [f64; 3],
223        points: Vec<[f32; 3]>,
224        tris: Vec<[u32; 3]>,
225        refs: Vec<u32>,
226    ) -> Result<Self> {
227        let vertex_live = full_bits(points.len())?;
228        let triangle_live = full_bits(tris.len())?;
229        Ok(Self::assemble(
230            origin,
231            points,
232            tris,
233            refs,
234            vertex_live,
235            triangle_live,
236        ))
237    }
238
239    fn assemble(
240        origin: [f64; 3],
241        points: Vec<[f32; 3]>,
242        tris: Vec<[u32; 3]>,
243        refs: Vec<u32>,
244        vertex_live: Vec<u64>,
245        triangle_live: Vec<u64>,
246    ) -> Self {
247        let (v, t) = (points.len(), tris.len());
248        Self {
249            uid: NEXT_MESH_UID.fetch_add(1, Ordering::Relaxed),
250            epoch: 0,
251            revision: 0,
252            next_revision: 0,
253            origin,
254            positions: Column::from_vec(points),
255            triangles: Column::from_vec(tris),
256            vertex_live: Column::from_vec(vertex_live),
257            triangle_live: Column::from_vec(triangle_live),
258            vertex_refs: Column::from_vec(refs),
259            channels: Vec::new(),
260            next_channel_id: 0,
261            vertex_free: Vec::new(),
262            triangle_free: Vec::new(),
263            live_vertices: v,
264            live_triangles: t,
265            adjacency: None,
266            incidence: None,
267            incidence_pending: false,
268        }
269    }
270
271    /// Process-unique id of this mesh. Patches only apply to the mesh that created them.
272    pub fn uid(&self) -> u64 {
273        self.uid
274    }
275
276    /// Changes on every commit, undo and redo. Equal revisions mean equal content.
277    pub fn revision(&self) -> u64 {
278        self.revision
279    }
280
281    /// Changes whenever [`Mesh::compact`] renumbers slots. Data indexed by slot id
282    /// (such as an acceleration structure) is stale once the epoch differs.
283    pub fn epoch(&self) -> u32 {
284        self.epoch
285    }
286
287    /// World-space origin that positions are relative to.
288    pub fn origin(&self) -> [f64; 3] {
289        self.origin
290    }
291
292    /// Number of vertex slots, alive or dead. Render buffers use this length.
293    pub fn vertex_slots(&self) -> usize {
294        self.positions.len()
295    }
296
297    /// Number of triangle slots, alive or dead.
298    pub fn triangle_slots(&self) -> usize {
299        self.triangles.len()
300    }
301
302    /// Number of alive vertices.
303    pub fn vertex_count(&self) -> usize {
304        self.live_vertices
305    }
306
307    /// Number of alive triangles.
308    pub fn triangle_count(&self) -> usize {
309        self.live_triangles
310    }
311
312    pub fn is_vertex_alive(&self, v: VertexId) -> bool {
313        bit(&self.vertex_live, v as usize)
314    }
315
316    pub fn is_triangle_alive(&self, t: TriangleId) -> bool {
317        bit(&self.triangle_live, t as usize)
318    }
319
320    /// Position relative to [`Mesh::origin`], or `None` for a dead or missing vertex.
321    pub fn position(&self, v: VertexId) -> Option<[f32; 3]> {
322        self.is_vertex_alive(v)
323            .then(|| self.positions.get(v as usize))
324    }
325
326    /// Position in world space (origin added in `f64`).
327    pub fn world_position(&self, v: VertexId) -> Option<[f64; 3]> {
328        self.position(v)
329            .map(|p| [0, 1, 2].map(|k| self.origin[k] + p[k] as f64))
330    }
331
332    /// Slot-aligned positions, including dead slots.
333    pub fn positions(&self) -> &[[f32; 3]] {
334        self.positions.as_slice()
335    }
336
337    /// Vertex ids of a triangle, or `None` for a dead or missing triangle.
338    pub fn triangle(&self, t: TriangleId) -> Option<[VertexId; 3]> {
339        self.is_triangle_alive(t)
340            .then(|| self.triangles.get(t as usize))
341    }
342
343    /// Slot-aligned triangles; dead slots read `[0, 0, 0]`.
344    pub fn triangles(&self) -> &[[VertexId; 3]] {
345        self.triangles.as_slice()
346    }
347
348    /// How many triangle corners use vertex `v` (a vertex repeated in one triangle counts twice).
349    pub fn corner_count(&self, v: VertexId) -> u32 {
350        if (v as usize) < self.vertex_refs.len() {
351            self.vertex_refs.get(v as usize)
352        } else {
353            0
354        }
355    }
356
357    /// Dead vertex slots ready for reuse.
358    pub fn free_vertex_slots(&self) -> &[VertexId] {
359        &self.vertex_free
360    }
361
362    /// Dead triangle slots ready for reuse.
363    pub fn free_triangle_slots(&self) -> &[TriangleId] {
364        &self.triangle_free
365    }
366
367    /// Adds a channel filled with its default value. Adding a channel is not undoable.
368    pub fn add_channel(&mut self, desc: ChannelDesc) -> Result<()> {
369        if !(1..=4).contains(&desc.width) {
370            return Err(StoreError::InvalidArgument("channel width must be 1 to 4"));
371        }
372        if desc.name == UV_CHANNEL && (desc.width != 2 || desc.kind != ChannelKind::Source) {
373            return Err(StoreError::InvalidArgument(
374                "the uv channel must be a source channel of width 2",
375            ));
376        }
377        if self.channel_index(&desc.name).is_some() {
378            return Err(StoreError::ChannelExists(desc.name));
379        }
380        let data = try_vec(self.vertex_slots() * desc.width, desc.default)?;
381        try_reserve(&mut self.channels, 1)?;
382        let id = self.next_channel_id;
383        self.next_channel_id += 1;
384        self.channels.push(Channel {
385            id,
386            desc,
387            data: Column::from_vec(data),
388            hidden: false,
389        });
390        Ok(())
391    }
392
393    /// Adds a channel holding `values` (`width` per vertex slot) from the
394    /// start. Like [`Mesh::add_channel`], not undoable and no edit: no undo
395    /// step and no journal entry, whatever the channel's size.
396    ///
397    /// # Errors
398    /// [`StoreError::LengthMismatch`] for a wrong length, or as for
399    /// [`Mesh::add_channel`]. The mesh is then unchanged.
400    pub fn add_channel_with_values(&mut self, desc: ChannelDesc, values: &[f32]) -> Result<()> {
401        let expected = self.vertex_slots() * desc.width;
402        if values.len() != expected {
403            return Err(StoreError::LengthMismatch {
404                expected,
405                got: values.len(),
406            });
407        }
408        self.add_channel(desc)?;
409        if let Some(channel) = self.channels.last_mut() {
410            channel.data.as_mut_slice().copy_from_slice(values);
411        }
412        Ok(())
413    }
414
415    /// Adds the UV channel ([`UV_CHANNEL`]) with `uvs`: 2 finite values per
416    /// vertex slot. Like [`Mesh::add_channel`], not undoable. Meant for a mesh
417    /// being set up, before any edit.
418    ///
419    /// # Errors
420    /// [`StoreError::LengthMismatch`] for a wrong length,
421    /// [`StoreError::InvalidArgument`] for a non-finite value,
422    /// [`StoreError::ChannelExists`] when the mesh already has UVs. The mesh is
423    /// then unchanged.
424    pub fn add_uvs(&mut self, uvs: &[f32]) -> Result<()> {
425        let expected = self.vertex_slots() * 2;
426        if uvs.len() != expected {
427            return Err(StoreError::LengthMismatch {
428                expected,
429                got: uvs.len(),
430            });
431        }
432        if !uvs.iter().all(|x| x.is_finite()) {
433            return Err(StoreError::InvalidArgument("uvs must be finite"));
434        }
435        if self.channel_index(UV_CHANNEL).is_some() {
436            return Err(StoreError::ChannelExists(UV_CHANNEL.into()));
437        }
438        let mut data: Vec<f32> = Vec::new();
439        try_reserve_exact(&mut data, uvs.len())?;
440        data.extend_from_slice(uvs);
441        try_reserve(&mut self.channels, 1)?;
442        let id = self.next_channel_id;
443        self.next_channel_id += 1;
444        self.channels.push(Channel {
445            id,
446            desc: ChannelDesc::uv(),
447            data: Column::from_vec(data),
448            hidden: false,
449        });
450        Ok(())
451    }
452
453    /// Slot-aligned UVs, 2 values per vertex, or `None` for a mesh without
454    /// them. Shorthand for `channel(UV_CHANNEL)`.
455    pub fn uvs(&self) -> Option<&[f32]> {
456        self.channel(UV_CHANNEL)
457    }
458
459    /// Removes a channel. Not undoable; patches that recorded it skip it from now on.
460    pub fn remove_channel(&mut self, name: &str) -> Result<ChannelDesc> {
461        let i = self
462            .channel_index(name)
463            .ok_or_else(|| StoreError::UnknownChannel(name.into()))?;
464        Ok(self.channels.remove(i).desc)
465    }
466
467    pub fn channel_desc(&self, name: &str) -> Option<&ChannelDesc> {
468        self.channel_index(name).map(|i| &self.channels[i].desc)
469    }
470
471    /// Slot-aligned values, `width` per vertex.
472    pub fn channel(&self, name: &str) -> Option<&[f32]> {
473        self.channel_index(name)
474            .map(|i| self.channels[i].data.as_slice())
475    }
476
477    /// Writable values of a derived channel. Writes are not recorded for undo.
478    pub fn derived_channel_mut(&mut self, name: &str) -> Result<&mut [f32]> {
479        let i = self
480            .channel_index(name)
481            .ok_or_else(|| StoreError::UnknownChannel(name.into()))?;
482        if self.channels[i].desc.kind != ChannelKind::Derived {
483            return Err(StoreError::NotDerivedChannel(name.into()));
484        }
485        Ok(self.channels[i].data.as_mut_slice())
486    }
487
488    pub(crate) fn channel_index(&self, name: &str) -> Option<usize> {
489        self.channels
490            .iter()
491            .position(|c| !c.hidden && c.desc.name == name)
492    }
493
494    /// Index of the channel with this id, or `None` if it was removed. Finds
495    /// hidden channels too.
496    pub(crate) fn channel_index_by_id(&self, id: u64) -> Option<usize> {
497        self.channels.iter().position(|c| c.id == id)
498    }
499
500    /// Puts dead vertex slots on the free list. Ids that are alive or out of range
501    /// are skipped: they are not free slots of this mesh.
502    pub(crate) fn release_vertices(&mut self, ids: &[VertexId]) {
503        for &v in ids {
504            if (v as usize) < self.vertex_slots() && !self.is_vertex_alive(v) {
505                self.vertex_free.push(v);
506            }
507        }
508    }
509
510    /// Puts dead triangle slots on the free list, skipping alive or out-of-range ids.
511    pub(crate) fn release_triangles(&mut self, ids: &[TriangleId]) {
512        for &t in ids {
513            if (t as usize) < self.triangle_slots() && !self.is_triangle_alive(t) {
514                self.triangle_free.push(t);
515            }
516        }
517    }
518
519    /// Returns the slots held by a [`Patch`] kept outside [`crate::History`] —
520    /// for example on an application's own undo stack or a scene-wide history —
521    /// to the free lists, applying the same rules the history uses internally:
522    /// [`PatchState::Applied`] releases the slots the patch removed,
523    /// [`PatchState::Undone`] the slots it created.
524    ///
525    /// Unlike [`Patch::release`], a patch from another mesh or from before a
526    /// [`Mesh::compact`] is an error ([`StoreError::PatchMismatch`] and
527    /// [`StoreError::StalePatch`], respectively), not a silent skip. Slots that
528    /// are alive — reused since the patch was made — are skipped.
529    ///
530    /// Release each patch at most once, and only once no history still holds it:
531    /// releasing twice, or releasing while a history still holds the patch,
532    /// lists a still-dead slot twice and corrupts slot reuse.
533    pub fn release_patch_slots(&mut self, patch: &Patch, state: PatchState) -> Result<()> {
534        if patch.mesh_uid != self.uid {
535            return Err(StoreError::PatchMismatch);
536        }
537        if patch.epoch != self.epoch {
538            return Err(StoreError::StalePatch);
539        }
540        let side = match state {
541            PatchState::Applied => PatchSide::Undoable,
542            PatchState::Undone => PatchSide::Redoable,
543        };
544        patch.release(self, side);
545        Ok(())
546    }
547
548    /// Called by every topology write inside a transaction.
549    pub(crate) fn on_topology_write(&mut self) {
550        self.adjacency = None;
551        self.incidence_pending = true;
552    }
553
554    /// Called when slots are renumbered: every topology cache is dropped.
555    pub(crate) fn on_slots_renumbered(&mut self) {
556        self.adjacency = None;
557        self.incidence = None;
558        self.incidence_pending = false;
559    }
560}
561
562/// The pass a [`MeshBuilder`] is running.
563#[derive(Debug, Clone, Copy, PartialEq, Eq)]
564enum BuildPhase {
565    /// Scanning the staged positions for finiteness.
566    Finite,
567    /// Scanning the bounding box.
568    Bounds,
569    /// Converting to `f32` offsets from the origin.
570    Convert,
571    /// Checking the indices and copying the triangles.
572    Indices,
573    /// Done: [`MeshBuilder::finish`] gives the mesh.
574    Done,
575}
576
577/// A [`Mesh`] built from flat buffers in steps, for callers that must yield
578/// between chunks of work (the Worker builds large meshes this way).
579///
580/// The inputs arrive first, in chunks of any size
581/// ([`MeshBuilder::push_positions`] and [`MeshBuilder::push_indices`]); then
582/// [`MeshBuilder::step`] runs the same passes as [`Mesh::from_buffers_f64`]
583/// (or [`Mesh::from_buffers_f64_with_origin`] with an explicit origin) a
584/// slice at a time: a finiteness scan, the bounding box (which sets the
585/// origin without one), the conversion to `f32` offsets, and the index check
586/// with the triangle copy. [`MeshBuilder::finish`] gives the mesh: the same
587/// one, bit for bit, as the one-call build, or the same error, however the
588/// chunks and steps split the work.
589#[derive(Debug)]
590pub struct MeshBuilder {
591    vertices: usize,
592    triangles: usize,
593    /// The origin the positions are stored relative to: the given one, or
594    /// the bounding-box centre once the bounds are scanned.
595    origin: [f64; 3],
596    /// Whether `origin` is still to be computed from the bounding box.
597    auto_origin: bool,
598    positions: Vec<f64>,
599    indices: Vec<u32>,
600    phase: BuildPhase,
601    min: [f64; 3],
602    max: [f64; 3],
603    points: Vec<[f32; 3]>,
604    tris: Vec<[u32; 3]>,
605    /// Zeroed alongside the conversion, so the index pass only counts.
606    refs: Vec<u32>,
607    vertex_live: Vec<u64>,
608    triangle_live: Vec<u64>,
609    /// The first vertex (the positions passes) or triangle (the index pass)
610    /// the current pass has not handled yet.
611    cursor: usize,
612}
613
614impl MeshBuilder {
615    /// A build of a mesh of `vertices` vertices and `triangles` triangles,
616    /// stored relative to `origin` (or the bounding-box centre without one),
617    /// with nothing uploaded yet. All the memory is reserved here, so the
618    /// pushes and steps allocate nothing except the small liveness bitsets.
619    ///
620    /// # Errors
621    /// [`StoreError::InvalidArgument`] for a non-finite origin (as
622    /// [`Mesh::from_buffers_f64_with_origin`]); [`StoreError::OutOfMemory`].
623    pub fn new(vertices: usize, triangles: usize, origin: Option<[f64; 3]>) -> Result<Self> {
624        let not_finite = StoreError::InvalidArgument("positions must be finite");
625        if let Some(o) = origin
626            && !o.iter().all(|x| x.is_finite())
627        {
628            return Err(not_finite);
629        }
630        // Checked: the steps multiply by 3 without checking.
631        let flat_vertices = vertices.checked_mul(3).ok_or(StoreError::OutOfMemory)?;
632        let flat_triangles = triangles.checked_mul(3).ok_or(StoreError::OutOfMemory)?;
633        let mut positions = Vec::new();
634        try_reserve_exact(&mut positions, flat_vertices)?;
635        let mut indices = Vec::new();
636        try_reserve_exact(&mut indices, flat_triangles)?;
637        let mut points = Vec::new();
638        try_reserve_exact(&mut points, vertices)?;
639        let mut tris = Vec::new();
640        try_reserve_exact(&mut tris, triangles)?;
641        let mut refs = Vec::new();
642        try_reserve_exact(&mut refs, vertices)?;
643        Ok(MeshBuilder {
644            vertices,
645            triangles,
646            origin: origin.unwrap_or([0.0; 3]),
647            auto_origin: origin.is_none(),
648            positions,
649            indices,
650            phase: BuildPhase::Finite,
651            min: [f64::INFINITY; 3],
652            max: [f64::NEG_INFINITY; 3],
653            points,
654            tris,
655            refs,
656            vertex_live: Vec::new(),
657            triangle_live: Vec::new(),
658            cursor: 0,
659        })
660    }
661
662    /// Appends a chunk of flat `[x, y, z, ...]` positions. Chunks may split
663    /// anywhere; they only must add up to the declared vertices.
664    ///
665    /// # Errors
666    /// [`StoreError::InvalidArgument`] for more than declared.
667    pub fn push_positions(&mut self, chunk: &[f64]) -> Result<()> {
668        if chunk.len() > 3 * self.vertices - self.positions.len() {
669            return Err(StoreError::InvalidArgument(
670                "positions exceed the declared vertex count",
671            ));
672        }
673        // Within the reserved capacity: no allocation.
674        self.positions.extend_from_slice(chunk);
675        Ok(())
676    }
677
678    /// Appends a chunk of flat `[a, b, c, ...]` indices; like
679    /// [`MeshBuilder::push_positions`], they must add up to the declared
680    /// triangles.
681    ///
682    /// # Errors
683    /// [`StoreError::InvalidArgument`] for more than declared.
684    pub fn push_indices(&mut self, chunk: &[u32]) -> Result<()> {
685        if chunk.len() > 3 * self.triangles - self.indices.len() {
686            return Err(StoreError::InvalidArgument(
687                "indices exceed the declared triangle count",
688            ));
689        }
690        self.indices.extend_from_slice(chunk);
691        Ok(())
692    }
693
694    /// Whether the mesh is built.
695    pub fn is_done(&self) -> bool {
696        self.phase == BuildPhase::Done
697    }
698
699    /// Runs up to `budget` more vertices (the positions passes) or triangles
700    /// (the index pass) of the build — at least one while any pass still has
701    /// vertices or triangles — and returns whether the mesh is built. The
702    /// upload must be complete first.
703    ///
704    /// # Errors
705    /// [`StoreError::InvalidArgument`] when the upload is incomplete; then
706    /// the same errors as [`Mesh::from_buffers_f64`]: non-finite input,
707    /// offsets that do not fit an `f32`, or [`StoreError::InvalidVertex`]
708    /// for the first out-of-range index; [`StoreError::OutOfMemory`]. The
709    /// build is unusable after an error.
710    pub fn step(&mut self, budget: usize) -> Result<bool> {
711        if self.positions.len() != 3 * self.vertices || self.indices.len() != 3 * self.triangles {
712            return Err(StoreError::InvalidArgument("upload incomplete"));
713        }
714        let not_finite = StoreError::InvalidArgument("positions must be finite");
715        let n = budget.max(1);
716        match self.phase {
717            BuildPhase::Finite => {
718                let end = self.cursor.saturating_add(n).min(self.vertices);
719                if !self.positions[3 * self.cursor..3 * end]
720                    .iter()
721                    .all(|x| x.is_finite())
722                {
723                    return Err(not_finite);
724                }
725                self.cursor = end;
726                if end == self.vertices {
727                    self.phase = BuildPhase::Bounds;
728                    self.cursor = 0;
729                }
730            }
731            BuildPhase::Bounds => {
732                let end = self.cursor.saturating_add(n).min(self.vertices);
733                for c in self.positions[3 * self.cursor..3 * end].as_chunks::<3>().0 {
734                    for (k, &x) in c.iter().enumerate() {
735                        self.min[k] = self.min[k].min(x);
736                        self.max[k] = self.max[k].max(x);
737                    }
738                }
739                self.cursor = end;
740                if end == self.vertices {
741                    if self.auto_origin {
742                        self.origin = if self.vertices == 0 {
743                            [0.0; 3]
744                        } else {
745                            [0, 1, 2].map(|k| (self.min[k] + self.max[k]) * 0.5)
746                        };
747                        self.auto_origin = false;
748                    }
749                    if !self.origin.iter().all(|x| x.is_finite()) {
750                        return Err(not_finite);
751                    }
752                    self.phase = BuildPhase::Convert;
753                    self.cursor = 0;
754                }
755            }
756            BuildPhase::Convert => {
757                let end = self.cursor.saturating_add(n).min(self.vertices);
758                for c in self.positions[3 * self.cursor..3 * end].as_chunks::<3>().0 {
759                    let p = [0, 1, 2].map(|k| (c[k] - self.origin[k]) as f32);
760                    if !p.iter().all(|x| x.is_finite()) {
761                        return Err(not_finite);
762                    }
763                    self.points.push(p);
764                }
765                self.refs.resize(end, 0);
766                self.cursor = end;
767                if end == self.vertices {
768                    self.phase = BuildPhase::Indices;
769                    self.cursor = 0;
770                }
771            }
772            BuildPhase::Indices => {
773                let end = self.cursor.saturating_add(n).min(self.triangles);
774                for c in self.indices[3 * self.cursor..3 * end].as_chunks::<3>().0 {
775                    for &v in c {
776                        if v as usize >= self.vertices {
777                            return Err(StoreError::InvalidVertex(v));
778                        }
779                        self.refs[v as usize] += 1;
780                    }
781                    self.tris.push(*c);
782                }
783                self.cursor = end;
784                if end == self.triangles {
785                    self.vertex_live = full_bits(self.vertices)?;
786                    self.triangle_live = full_bits(self.triangles)?;
787                    self.phase = BuildPhase::Done;
788                }
789            }
790            BuildPhase::Done => return Ok(true),
791        }
792        Ok(self.phase == BuildPhase::Done)
793    }
794
795    /// The mesh, once [`MeshBuilder::step`] returned `true`; `None` before.
796    pub fn finish(self) -> Option<Mesh> {
797        if self.phase != BuildPhase::Done {
798            return None;
799        }
800        Some(Mesh::assemble(
801            self.origin,
802            self.points,
803            self.tris,
804            self.refs,
805            self.vertex_live,
806            self.triangle_live,
807        ))
808    }
809}
810
811#[cfg(test)]
812mod tests {
813    use super::*;
814    use crate::PatchState;
815
816    /// A builder twin-test case: flat positions, flat indices, the origin.
817    type TwinCase = (Vec<f64>, Vec<u32>, Option<[f64; 3]>);
818    /// A builder failure case, with the error both builds return.
819    type FailCase = (Vec<f64>, Vec<u32>, Option<[f64; 3]>, StoreError);
820
821    fn quad() -> Mesh {
822        Mesh::from_buffers(
823            &[0., 0., 0., 1., 0., 0., 1., 1., 0., 0., 1., 0.],
824            &[0, 1, 2, 0, 2, 3],
825        )
826        .unwrap()
827    }
828
829    #[test]
830    fn builds_from_flat_buffers() {
831        let m = quad();
832        assert_eq!((m.vertex_count(), m.triangle_count()), (4, 2));
833        assert_eq!(m.position(2), Some([1., 1., 0.]));
834        assert_eq!(m.triangle(1), Some([0, 2, 3]));
835        assert_eq!(m.corner_count(0), 2);
836        assert_eq!(m.corner_count(1), 1);
837    }
838
839    #[test]
840    fn rejects_bad_buffers() {
841        assert!(matches!(
842            Mesh::from_buffers(&[0., 0.], &[]),
843            Err(StoreError::InvalidArgument(_))
844        ));
845        assert_eq!(
846            Mesh::from_buffers(&[0., 0., 0.], &[0, 0, 5]).err(),
847            Some(StoreError::InvalidVertex(5))
848        );
849    }
850
851    #[test]
852    fn f64_input_keeps_precision_through_the_origin() {
853        let far = 1.0e7;
854        let m = Mesh::from_buffers_f64(&[far, far, 0.0, far + 0.001, far, 0.0], &[]).unwrap();
855        let o = m.origin();
856        assert!((o[0] - (far + 0.0005)).abs() < 1e-6 && o[1] == far && o[2] == 0.0);
857        let p = m.world_position(1).unwrap();
858        assert!((p[0] - (far + 0.001)).abs() < 1e-6, "got {}", p[0]);
859    }
860
861    #[test]
862    fn f64_input_must_be_finite() {
863        let bad = StoreError::InvalidArgument("positions must be finite");
864        for x in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
865            assert_eq!(
866                Mesh::from_buffers_f64(&[0., 0., 0., x, 1., 2.], &[]).err(),
867                Some(bad.clone())
868            );
869        }
870        // Finite input whose bounding-box center overflows.
871        assert_eq!(
872            Mesh::from_buffers_f64(&[f64::MAX, 0., 0., f64::MAX, 0., 0.], &[]).err(),
873            Some(bad)
874        );
875    }
876
877    #[test]
878    fn explicit_origin_stores_offsets() {
879        let o = [1.0e6; 3];
880        let offsets = [[0.25, -0.5, 1.0], [-3.0, 2.125, 0.0], [10.0, 0.0, -7.5]];
881        let flat: Vec<f64> = offsets
882            .iter()
883            .flat_map(|d| [0, 1, 2].map(|k| o[k] + d[k]))
884            .collect();
885        let m = Mesh::from_buffers_f64_with_origin(&flat, &[0, 1, 2], o).unwrap();
886        assert_eq!(m.origin(), o);
887        for (v, d) in offsets.iter().enumerate() {
888            let p = m.position(v as u32).unwrap();
889            for k in 0..3 {
890                assert!((p[k] as f64 - d[k]).abs() < 1e-3, "{v}: {p:?} vs {d:?}");
891            }
892        }
893        let bad = StoreError::InvalidArgument("positions must be finite");
894        assert_eq!(
895            Mesh::from_buffers_f64_with_origin(&flat, &[], [f64::NAN, 0., 0.]).err(),
896            Some(bad.clone())
897        );
898        assert_eq!(
899            Mesh::from_buffers_f64_with_origin(&[f64::INFINITY, 0., 0.], &[], [0.; 3]).err(),
900            Some(bad)
901        );
902        assert!(matches!(
903            Mesh::from_buffers_f64_with_origin(&[0., 0.], &[], [0.; 3]),
904            Err(StoreError::InvalidArgument(_))
905        ));
906    }
907
908    #[test]
909    fn offsets_overflowing_f32_are_rejected() {
910        let bad = StoreError::InvalidArgument("positions must be finite");
911        // Finite in f64, but 1e39 away from the origin does not fit an f32.
912        assert_eq!(
913            Mesh::from_buffers_f64_with_origin(&[1.0e39, 0., 0.], &[], [0.; 3]).err(),
914            Some(bad.clone())
915        );
916        assert_eq!(
917            Mesh::from_buffers_f64_with_origin(&[0., 0., 0.], &[], [0., -1.0e39, 0.]).err(),
918            Some(bad.clone())
919        );
920        // The auto-centred constructor shares the check.
921        assert_eq!(
922            Mesh::from_buffers_f64(&[-1.0e300, 0., 0., 1.0e300, 0., 0.], &[]).err(),
923            Some(bad)
924        );
925    }
926
927    #[test]
928    fn out_of_range_ids_read_as_dead() {
929        let m = quad();
930        assert!(!m.is_vertex_alive(99));
931        assert_eq!(m.position(99), None);
932        assert_eq!(m.triangle(99), None);
933    }
934
935    #[test]
936    fn channels_are_slot_aligned_and_unique() {
937        let mut m = quad();
938        m.add_channel(ChannelDesc::source("mask", 1).with_default(0.5))
939            .unwrap();
940        assert_eq!(m.channel("mask"), Some(&[0.5; 4][..]));
941        assert_eq!(
942            m.add_channel(ChannelDesc::source("mask", 1)),
943            Err(StoreError::ChannelExists("mask".into()))
944        );
945        assert!(matches!(
946            m.add_channel(ChannelDesc::source("wide", 5)),
947            Err(StoreError::InvalidArgument(_))
948        ));
949        assert_eq!(
950            m.derived_channel_mut("mask").err(),
951            Some(StoreError::NotDerivedChannel("mask".into()))
952        );
953        m.add_channel(ChannelDesc::derived("dist", 1)).unwrap();
954        m.derived_channel_mut("dist").unwrap()[3] = 7.0;
955        assert_eq!(m.channel("dist").unwrap()[3], 7.0);
956        assert_eq!(m.remove_channel("dist").unwrap().name, "dist");
957        assert_eq!(m.channel("dist"), None);
958    }
959
960    #[test]
961    fn debug_output_shows_counts() {
962        let mut m = quad();
963        m.ensure_adjacency().unwrap();
964        let s = format!("{m:?}");
965        assert!(
966            s.contains("vertices: 4") && s.contains("triangles: 2"),
967            "{s}"
968        );
969        assert!(format!("{:?}", m.adjacency().unwrap()).starts_with("Adjacency"));
970    }
971
972    #[test]
973    fn every_mesh_gets_a_distinct_uid() {
974        assert_ne!(Mesh::new().uid(), Mesh::new().uid());
975    }
976
977    #[test]
978    fn release_patch_slots_follows_history_rules() {
979        let mut m = quad();
980        let mut tx = m.begin_edit();
981        tx.remove_triangles(&[1]).unwrap();
982        tx.remove_vertices(&[3]).unwrap();
983        let removal = tx.commit().unwrap();
984        assert!(m.free_vertex_slots().is_empty(), "reserved by the patch");
985        // The wrong state releases nothing.
986        m.release_patch_slots(&removal, PatchState::Undone).unwrap();
987        assert!(m.free_vertex_slots().is_empty());
988        m.release_patch_slots(&removal, PatchState::Applied)
989            .unwrap();
990        assert_eq!(m.free_vertex_slots(), &[3]);
991        assert_eq!(m.free_triangle_slots(), &[1]);
992
993        let mut tx = m.begin_edit();
994        tx.add_vertices(&[[0.; 3]]).unwrap();
995        let creation = tx.commit().unwrap();
996        creation.revert(&mut m).unwrap();
997        assert!(m.free_vertex_slots().is_empty(), "reserved for redo");
998        m.release_patch_slots(&creation, PatchState::Applied)
999            .unwrap();
1000        assert!(m.free_vertex_slots().is_empty());
1001        m.release_patch_slots(&creation, PatchState::Undone)
1002            .unwrap();
1003        assert_eq!(m.free_vertex_slots(), &[3]);
1004    }
1005
1006    #[test]
1007    fn release_patch_slots_rejects_a_foreign_mesh() {
1008        let mut m = quad();
1009        let mut other = quad();
1010        let mut tx = other.begin_edit();
1011        tx.remove_triangles(&[1]).unwrap();
1012        tx.remove_vertices(&[3]).unwrap();
1013        let patch = tx.commit().unwrap();
1014        assert_eq!(
1015            m.release_patch_slots(&patch, PatchState::Applied).err(),
1016            Some(StoreError::PatchMismatch)
1017        );
1018        assert!(m.free_vertex_slots().is_empty());
1019        assert!(m.free_triangle_slots().is_empty());
1020    }
1021
1022    #[test]
1023    fn release_patch_slots_rejects_an_earlier_epoch() {
1024        let mut m = quad();
1025        let mut tx = m.begin_edit();
1026        tx.remove_triangles(&[1]).unwrap();
1027        tx.remove_vertices(&[3]).unwrap();
1028        let patch = tx.commit().unwrap();
1029        m.compact().unwrap();
1030        assert_eq!(
1031            m.release_patch_slots(&patch, PatchState::Applied).err(),
1032            Some(StoreError::StalePatch)
1033        );
1034        assert!(m.free_vertex_slots().is_empty());
1035        assert!(m.free_triangle_slots().is_empty());
1036    }
1037
1038    #[test]
1039    fn release_patch_slots_skips_alive_slots() {
1040        let mut m = quad();
1041        let mut tx = m.begin_edit();
1042        tx.remove_triangles(&[1]).unwrap();
1043        tx.remove_vertices(&[3]).unwrap();
1044        let patch = tx.commit().unwrap();
1045        m.release_patch_slots(&patch, PatchState::Applied).unwrap();
1046        assert_eq!(m.free_vertex_slots(), &[3]);
1047        // Reuse the slots, then release again: the alive slots are skipped, so
1048        // a second release after reuse is a safe no-op.
1049        let mut tx = m.begin_edit();
1050        assert_eq!(tx.add_vertices(&[[0.; 3]]).unwrap(), vec![3]);
1051        assert_eq!(tx.add_triangles(&[[0, 1, 2]]).unwrap(), vec![1]);
1052        tx.commit().unwrap();
1053        m.release_patch_slots(&patch, PatchState::Applied).unwrap();
1054        assert!(m.free_vertex_slots().is_empty());
1055        assert!(m.free_triangle_slots().is_empty());
1056        let mut tx = m.begin_edit();
1057        assert_eq!(tx.add_vertices(&[[0.; 3]]).unwrap(), vec![4]);
1058        assert_eq!(tx.add_triangles(&[[0, 1, 2]]).unwrap(), vec![2]);
1059        tx.rollback();
1060    }
1061
1062    #[test]
1063    fn release_patch_slots_with_history_holding_the_patch() {
1064        use crate::History;
1065        let mut m = quad();
1066        let mut tx = m.begin_edit();
1067        tx.remove_triangles(&[1]).unwrap();
1068        tx.remove_vertices(&[3]).unwrap();
1069        let patch = tx.commit().unwrap();
1070        let mut h = History::new(1 << 20);
1071        h.push(&mut m, patch.clone()).unwrap();
1072        // An external undo stack drops its copy first; the slots are reused, so
1073        // when the history later releases the same patch its slots are alive
1074        // and skipped: no duplicate free-list entries.
1075        m.release_patch_slots(&patch, PatchState::Applied).unwrap();
1076        let mut tx = m.begin_edit();
1077        assert_eq!(tx.add_vertices(&[[0.; 3]]).unwrap(), vec![3]);
1078        assert_eq!(tx.add_triangles(&[[0, 1, 2]]).unwrap(), vec![1]);
1079        tx.commit().unwrap();
1080        h.clear(&mut m).unwrap();
1081        assert!(m.free_vertex_slots().is_empty());
1082        assert!(m.free_triangle_slots().is_empty());
1083        let mut tx = m.begin_edit();
1084        assert_eq!(tx.add_vertices(&[[0.; 3]]).unwrap(), vec![4]);
1085        assert_eq!(tx.add_triangles(&[[0, 1, 2]]).unwrap(), vec![2]);
1086        tx.rollback();
1087    }
1088    /// An `n × n` grid over `[0, n)²` with a wavy height, as flat `f64`
1089    /// positions and flat indices.
1090    fn grid_buffers(n: u32) -> (Vec<f64>, Vec<u32>) {
1091        let mut positions = Vec::new();
1092        for j in 0..n {
1093            for i in 0..n {
1094                let (x, y) = (f64::from(i), f64::from(j));
1095                positions.extend([x, y, 0.3 * (1.7 * x).sin() * (1.3 * y).cos()]);
1096            }
1097        }
1098        let mut indices = Vec::new();
1099        for j in 0..n - 1 {
1100            for i in 0..n - 1 {
1101                let a = j * n + i;
1102                indices.extend([a, a + 1, a + n + 1, a, a + n + 1, a + n]);
1103            }
1104        }
1105        (positions, indices)
1106    }
1107
1108    fn assert_same_mesh(got: &Mesh, want: &Mesh) {
1109        assert_eq!(
1110            got.origin().map(f64::to_bits),
1111            want.origin().map(f64::to_bits)
1112        );
1113        assert_eq!(got.positions(), want.positions());
1114        assert_eq!(got.triangles(), want.triangles());
1115        assert_eq!(got.vertex_count(), want.vertex_count());
1116        assert_eq!(got.triangle_count(), want.triangle_count());
1117        assert_eq!(got.vertex_slots(), want.vertex_slots());
1118        assert_eq!(got.triangle_slots(), want.triangle_slots());
1119        for v in 0..got.vertex_slots() as VertexId {
1120            assert_eq!(got.is_vertex_alive(v), want.is_vertex_alive(v));
1121            assert_eq!(got.corner_count(v), want.corner_count(v));
1122        }
1123        for t in 0..got.triangle_slots() as TriangleId {
1124            assert_eq!(got.is_triangle_alive(t), want.is_triangle_alive(t));
1125        }
1126    }
1127
1128    fn build_stepped(
1129        positions: &[f64],
1130        indices: &[u32],
1131        origin: Option<[f64; 3]>,
1132        budget: usize,
1133        chunk: usize,
1134    ) -> Result<Mesh> {
1135        let mut build = MeshBuilder::new(positions.len() / 3, indices.len() / 3, origin)?;
1136        for c in positions.chunks(chunk) {
1137            build.push_positions(c)?;
1138        }
1139        for c in indices.chunks(chunk) {
1140            build.push_indices(c)?;
1141        }
1142        assert!(!build.is_done());
1143        while !build.step(budget)? {}
1144        assert!(build.is_done());
1145        Ok(build.finish().expect("done builds finish"))
1146    }
1147
1148    #[test]
1149    fn mesh_builder_matches_from_buffers_f64() {
1150        let far = 1.0e7;
1151        let (grid_p, grid_t) = grid_buffers(30);
1152        let cases: Vec<TwinCase> = vec![
1153            (
1154                vec![0., 0., 0., 1., 0., 0., 1., 1., 0., 0., 1., 0.],
1155                vec![0, 1, 2, 0, 2, 3],
1156                None,
1157            ),
1158            (
1159                vec![0., 0., 0., 1., 0., 0., 1., 1., 0., 0., 1., 0.],
1160                vec![0, 1, 2, 0, 2, 3],
1161                Some([0.5, 0.5, 0.0]),
1162            ),
1163            (vec![], vec![], None),
1164            (vec![], vec![], Some([1., 2., 3.])),
1165            (vec![1., 2., 3.], vec![], None),
1166            (vec![far, far, 0.0, far + 0.001, far, 0.0], vec![], None),
1167            (grid_p.clone(), grid_t.clone(), None),
1168            (grid_p, grid_t, Some([15.0, 15.0, 0.0])),
1169        ];
1170        for (positions, indices, origin) in &cases {
1171            let reference = match origin {
1172                None => Mesh::from_buffers_f64(positions, indices).unwrap(),
1173                Some(o) => Mesh::from_buffers_f64_with_origin(positions, indices, *o).unwrap(),
1174            };
1175            for budget in [1, 2, 7, 100, usize::MAX] {
1176                // Chunk sizes deliberately not multiples of 3.
1177                for chunk in [1, 5, 100] {
1178                    let got = build_stepped(positions, indices, *origin, budget, chunk).unwrap();
1179                    assert_same_mesh(&got, &reference);
1180                }
1181            }
1182        }
1183    }
1184
1185    #[test]
1186    fn mesh_builder_fails_like_from_buffers_f64() {
1187        let bad = StoreError::InvalidArgument("positions must be finite");
1188        let (mut grid_p, mut grid_t) = grid_buffers(10);
1189        // A failure past the first step at small budgets: the middle of each buffer.
1190        grid_p[3 * 50 + 1] = f64::NAN;
1191        grid_t[3 * 60 + 2] = 10_000;
1192        let cases: Vec<FailCase> = vec![
1193            (grid_p, grid_t.clone(), None, bad.clone()),
1194            {
1195                let (p, _) = grid_buffers(10);
1196                (p, grid_t, None, StoreError::InvalidVertex(10_000))
1197            },
1198            (
1199                vec![f64::MAX, 0., 0., f64::MAX, 0., 0.],
1200                vec![],
1201                None,
1202                bad.clone(),
1203            ),
1204            (vec![1.0e39, 0., 0.], vec![], Some([0.; 3]), bad.clone()),
1205            (
1206                vec![0., 0., 0.],
1207                vec![],
1208                Some([f64::NAN, 0., 0.]),
1209                bad.clone(),
1210            ),
1211            (vec![], vec![7, 8, 9], None, StoreError::InvalidVertex(7)),
1212        ];
1213        for (positions, indices, origin, expected) in &cases {
1214            let reference = match origin {
1215                None => Mesh::from_buffers_f64(positions, indices).err(),
1216                Some(o) => Mesh::from_buffers_f64_with_origin(positions, indices, *o).err(),
1217            };
1218            assert_eq!(reference, Some(expected.clone()));
1219            for budget in [1, 7, usize::MAX] {
1220                let err = build_stepped(positions, indices, *origin, budget, 5).err();
1221                assert_eq!(err, Some(expected.clone()));
1222            }
1223        }
1224    }
1225
1226    #[test]
1227    fn mesh_builder_upload_protocol() {
1228        // Pushing past the declared counts fails.
1229        let mut build = MeshBuilder::new(1, 1, None).unwrap();
1230        build.push_positions(&[0., 0., 0.]).unwrap();
1231        assert!(matches!(
1232            build.push_positions(&[1.]),
1233            Err(StoreError::InvalidArgument(_))
1234        ));
1235        assert!(matches!(
1236            build.push_indices(&[0, 0, 0, 0]),
1237            Err(StoreError::InvalidArgument(_))
1238        ));
1239        // Stepping before the upload is complete fails.
1240        assert!(matches!(
1241            build.step(100),
1242            Err(StoreError::InvalidArgument(_))
1243        ));
1244        assert!(!build.is_done());
1245        assert!(MeshBuilder::new(1, 1, None).unwrap().finish().is_none());
1246        // Empty pushes are no-ops.
1247        build.push_positions(&[]).unwrap();
1248        build.push_indices(&[]).unwrap();
1249        build.push_indices(&[0, 0, 0]).unwrap();
1250        while !build.step(1).unwrap() {}
1251        assert!(build.step(1).unwrap());
1252        let mesh = build.finish().expect("done builds finish");
1253        assert_eq!((mesh.vertex_count(), mesh.triangle_count()), (1, 1));
1254        // Absurd counts fail without allocating.
1255        assert_eq!(
1256            MeshBuilder::new(usize::MAX / 3 + 1, 0, None).err(),
1257            Some(StoreError::OutOfMemory)
1258        );
1259        assert_eq!(
1260            MeshBuilder::new(0, usize::MAX / 3 + 1, None).err(),
1261            Some(StoreError::OutOfMemory)
1262        );
1263    }
1264}