Skip to main content

fast_mesh_scene/
fields.rs

1//! Distance fields between meshes of the scene, and the colours derived
2//! from them (spec §6, §11).
3//!
4//! A field's values live in a derived store channel `field:<name>` on mesh
5//! A (`on`), one `f32` per vertex slot: the signed distance to the nearest
6//! of its target meshes (`to`) as placed in the world, `+∞` beyond
7//! `max_distance`, NaN for dead slots and for alive vertices no alive
8//! triangle uses. "Nearest" is the smallest signed value: the nearest
9//! surface outside every target, or the deepest penetration. The field also
10//! keeps, per vertex slot, the index in `to` of the target that value came
11//! from (ties to the lowest index; none for `+∞` and NaN). With one target,
12//! the values are the distances to that mesh.
13//!
14//! **What an update recomputes** (§11.6). Each field has a change
15//! accumulator on A and on every target (a [`FieldHook`], fed like a
16//! subscriber's report). Everything is recomputed when any says "everything"
17//! (`add_mesh`, `compact`, a `Corrupted` step), when `max_distance` is
18//! infinite, or on the first update. Otherwise a target is *moved* when A's
19//! placement changed, its own placement changed, or it was edited, and:
20//!
21//! - for each moved target B: the vertices of A's triangles within
22//!   `pad(max_distance)` of B (`max_distance` plus a relative `1e-6` and
23//!   `8·f32::EPSILON` times the coordinate magnitude of the pair), plus the
24//!   vertices whose finite value came from B (so they can return to far, or
25//!   go to another target);
26//! - after an edit of A: its added, moved and removed vertices, plus, when
27//!   its triangles changed, the vertices whose "used by a triangle" state
28//!   flipped.
29//!
30//! When the near search for a target would list more than
31//! [`NEAR_PAIRS_PER_SLOT`] pairs of triangles per vertex slot of A, it is
32//! abandoned and every vertex is recomputed instead: at a wide
33//! `max_distance` each triangle of A is near hundreds of B's, and listing the
34//! pairs costs far more than the recompute. Recomputing a vertex outside the
35//! near set gives it the value it already holds, so the values, colours and
36//! reports are the same.
37//!
38//! A vertex's value depends only on its own position and on the targets.
39//! Outside a moved target's new near set, that target's value is `+∞`, which
40//! never wins; so a vertex whose value came from a target that did not move
41//! keeps it (a moved target that tied with it had a higher index, or it would
42//! have won). Rebuilding a target's sign data after an edit can flip signs
43//! only of its finite values, which its near set covers. This relies on undo
44//! reporting revived slots as added (the store's `ChangeSet::merge` reports a
45//! reused slot as moved).
46//!
47//! **Removing a target mesh** removes it from the targets of every field
48//! that has others (such a field is recomputed in full on its next update),
49//! and removes the fields left without targets.
50//!
51//! **Several fields in one update** are prepared one after the other (BVHs,
52//! sign data), then computed without writing, in parallel across fields with
53//! the `parallel` feature when there is enough work, then applied in field
54//! order. Values, colours and reports are the same, bit for bit, as when
55//! each field is updated alone in turn.
56//!
57//! **Stepped updates** ([`Scene::field_step`]) run the same parts in steps:
58//! the due Eager fields' BVHs are prepared when the unit opens; then, one
59//! field after the other, the targets' sign data are built
60//! ([`SignBuild`], a bounded number of elements per step), the near
61//! searches run ([`NearSearch`], whole parts per step), the candidates are
62//! picked and their values computed a few at a time; the fields are
63//! applied together after the last. No value, colour, report or hook is
64//! written before that, so dropping the unit leaves none behind (the BVHs
65//! the unit brought up to date and the sign data it finished stay so, as
66//! after a query; a sign data build it left unfinished is kept with its
67//! field and resumed by a later unit while its mesh and BVH are
68//! unchanged). Each part gives the same bits in steps as in one call, so a
69//! stepped update equals a one-call one.
70
71use fast_mesh_bvh::Bvh;
72use fast_mesh_fields::{
73    ColorRule, FieldsError, SignBuild, SignData, SignMode, SignStatus, Target, color_of,
74    curvatures, signed_distances, signed_distances_multi, thicknesses,
75};
76use fast_mesh_query::{NearSearch, Rigid};
77use fast_mesh_store::{ChangeSet, ChannelDesc, Mesh, StoreError, TriangleId, VertexId, par};
78
79use crate::baked::baked_mesh;
80
81use crate::query::round_up;
82use crate::scene::{Entry, FIELD_PREFIX};
83use crate::{FieldId, MeshId, Result, Scene, SceneError, UpdateMode};
84
85/// A signed distance field to register with
86/// [`Scene::add_distance_field`](crate::Scene::add_distance_field).
87#[derive(Debug, Clone, PartialEq)]
88pub struct DistanceField {
89    /// The field's name, unique among the fields on mesh `on`.
90    pub name: String,
91    /// The mesh whose vertices carry the values (mesh A).
92    pub on: MeshId,
93    /// The meshes the distances are measured to: at least one, all
94    /// different, and not `on`. A vertex's value is the smallest over them,
95    /// ties going to the lowest index (see
96    /// [`Scene::field_nearest`](crate::Scene::field_nearest)).
97    pub to: Vec<MeshId>,
98    /// The largest distance computed (inclusive); vertices farther away hold
99    /// `+∞`. Zero or greater, may be infinite.
100    pub max_distance: f64,
101    /// How the sign is decided on each mesh of `to`.
102    pub sign: SignMode,
103}
104
105impl DistanceField {
106    /// A field on `on` measured to the single mesh `to`.
107    pub fn new(
108        name: impl Into<String>,
109        on: MeshId,
110        to: MeshId,
111        max_distance: f64,
112        sign: SignMode,
113    ) -> DistanceField {
114        DistanceField {
115            name: name.into(),
116            on,
117            to: vec![to],
118            max_distance,
119            sign,
120        }
121    }
122}
123
124/// A per-vertex curvature field to register with
125/// [`Scene::add_curvature_field`].
126///
127/// The value at a vertex is its signed mean curvature (the cotangent Laplace
128/// estimate of [`fast_mesh_fields::curvatures`]): positive on domes (convex
129/// as seen from the normal side, about `1 / r` on a sphere of radius `r`
130/// with outward normals), negative in bowls and dents, zero on flat spots;
131/// a saddle takes the sign of its dominant bend. Units are 1/world-unit:
132/// the placement's scale is applied (a mesh scaled by `s` reads `H / s`),
133/// so scaled meshes read scaled. Dead slots and alive vertices no alive
134/// triangle uses read NaN. Like a thickness field, the values live in a
135/// derived store channel `field:<name>` on `on` and colour through
136/// [`Scene::set_coloring`] unchanged.
137#[derive(Debug, Clone, PartialEq)]
138pub struct CurvatureField {
139    /// The field's name, unique among the fields on `on`.
140    pub name: String,
141    /// The mesh whose vertices carry the values.
142    pub on: MeshId,
143}
144
145/// A per-vertex wall-thickness field to register with
146/// [`Scene::add_thickness_field`].
147///
148/// The value at a vertex is the distance to the opposite surface along the
149/// vertex's smooth normal (the nearest ray hit in either normal direction),
150/// in world units: the placement's scale is applied, so scaled meshes read
151/// scaled. Hits beyond `max_distance` read `+∞`; vertices whose rays escape
152/// in both directions (open sheets, the rims of open meshes), dead slots and
153/// alive vertices no alive triangle uses read NaN. Like a distance field,
154/// the values live in a derived store channel `field:<name>` on `on` and
155/// colour through [`Scene::set_coloring`] unchanged.
156#[derive(Debug, Clone, PartialEq)]
157pub struct ThicknessField {
158    /// The field's name, unique among the fields on `on`.
159    pub name: String,
160    /// The mesh whose vertices carry the values.
161    pub on: MeshId,
162    /// The largest thickness kept (inclusive); thicker vertices hold `+∞`.
163    /// Zero or greater, may be infinite.
164    pub max_distance: f64,
165}
166
167/// The state of a field, from
168/// [`Scene::field_status`](crate::Scene::field_status).
169#[derive(Debug, Clone, Copy, PartialEq)]
170pub struct FieldStatus {
171    /// True when the values match the meshes and placements as they are now.
172    pub current: bool,
173    /// What the latest sign data builds found out about the meshes of `to`,
174    /// together: `closed` and `consistent_orientation` when every target is,
175    /// the counts and the signed volume summed over the targets, and the
176    /// first target's `mode` (every target is built in the requested mode,
177    /// so they share it unless `Auto` resolved differently). See
178    /// [`Scene::field_target_status`](crate::Scene::field_target_status) for
179    /// each target.
180    pub sign: SignStatus,
181}
182
183/// How a mesh is coloured from one of its fields, for
184/// [`Scene::set_coloring`](crate::Scene::set_coloring).
185#[derive(Debug, Clone, PartialEq)]
186pub struct Coloring {
187    /// The field, which must be on the coloured mesh.
188    pub field: FieldId,
189    /// How values become colours.
190    pub rule: ColorRule,
191}
192
193/// The near-set margin for `max_distance` `x` (§11.6), for meshes of
194/// coordinate magnitude `m` (see [`Scene::pair_magnitude`]): slightly
195/// larger, so that the pair test in A's space never drops a vertex the
196/// `f32` kernel in B's space keeps.
197fn pad(x: f64, m: f64) -> f64 {
198    x * (1.0 + 1e-6) + 8.0 * f64::from(f32::EPSILON) * m
199}
200
201/// The largest absolute coordinate of a box's corners.
202fn box_magnitude(min: [f32; 3], max: [f32; 3]) -> f64 {
203    min.iter()
204        .chain(&max)
205        .map(|c| f64::from(c.abs()))
206        .fold(0.0, f64::max)
207}
208
209fn oom() -> SceneError {
210    SceneError::Store(StoreError::OutOfMemory)
211}
212
213/// Work, summed over the fields due in one update (see [`FieldJob::work`]),
214/// from which those fields are computed in parallel, one field per task,
215/// with the `parallel` feature. Each field's kernel call is also parallel on
216/// its own above its own threshold.
217///
218/// Measured on the i5-13600K with six terrains in a row, each overlapping
219/// the next, a field for every ordered pair, dragging the middle one (ten
220/// fields due per pose): at 968 triangles per mesh (work about 10k) parallel
221/// is 1.3 times slower, at 1,352 (about 14k) 1.7 times faster, and at 30k
222/// 2.4 times faster.
223const PAR_MIN_FIELD_WORK: usize = 12 * 1024;
224
225/// Near pairs of triangles per vertex slot of A beyond which a field's near
226/// search is abandoned for recomputing every vertex (see the module docs).
227///
228/// Measured on the i5-13600K with two 1M-triangle terrains, B flipped over
229/// A: a near pair costs about 70 ns to find, and recomputing every vertex
230/// about 450 ms (0.9 µs a slot) at `max_distance` 8, where the search finds
231/// hundreds of pairs per slot and took 14 s. At one pair per slot, an
232/// abandoned search wastes about 8% of the recompute that replaces it. The
233/// contact drag (`max_distance` 0.08) and the many-mesh drags (0.5 between
234/// 30k-triangle terrains) stay under the limit and keep the near search;
235/// a quarter of a pair per slot already sends the latter to the slower
236/// recompute.
237const NEAR_PAIRS_PER_SLOT: usize = 1;
238
239/// Sign data elements (triangle, vertex or corner slots of a pass of the
240/// targets' adjacency, incidence or sign data) one point of a
241/// [`FieldBudget::Points`] budget stands for, in a step that builds sign
242/// data.
243///
244/// Measured on the i5-13600K, single-threaded, 1M triangles: a pseudonormal
245/// sign data build with its adjacency and incidence is about 7M elements in
246/// 220 ms (about 30 ns each), and a wide value about 1 µs, so 32 elements
247/// take about as long as a point.
248const SIGN_ELEMS_PER_POINT: usize = 32;
249
250/// Sign data elements of a build that one moved vertex of a refit stands for,
251/// in a step (see [`SIGN_ELEMS_PER_POINT`]): measured, a refit costs a few µs
252/// per moved vertex (its triangles' vertex and edge normals) where an element
253/// of a build costs about 30 ns. A larger edit builds in steps instead.
254const SIGN_ELEMS_PER_REFIT_VERTEX: usize = 4;
255
256/// Triangle pairs a near search tests per point of a
257/// [`FieldBudget::Points`] budget, in a step that searches: a test costs
258/// about 70 ns where most pairs are near (see [`NEAR_PAIRS_PER_SLOT`]), so
259/// 16 take about as long as a point. A step searches whole parts of the
260/// search, so it may test more.
261const NEAR_TESTS_PER_POINT: usize = 16;
262
263/// The share of [`Scene::set_field_interaction`]'s budget that goes to a
264/// sweep over the vertices outside the near set, a run of consecutive slots per
265/// update, so that the colours away from the contact keep following the motion.
266/// Consecutive slots are mostly neighbours on the surface: a run reads as a
267/// band passing over the mesh, where scattered vertices read as speckle.
268const LIVE_SWEEP_SHARE: usize = 8;
269
270/// The share of [`Scene::set_field_interaction`]'s budget for the vertices an
271/// edit moved, and again for those around an edit of a target.
272const LIVE_EDIT_SHARE: usize = 4;
273
274/// The first band of the interaction tier's near vertices: this fraction of
275/// the diagonal of A's box.
276const LIVE_FIRST_MARGIN: f64 = 1.0 / 256.0;
277
278/// The interaction tier's band grows by this factor after an update under
279/// half of the budget (over it, it is the budget's last vertex).
280const LIVE_GROW: f64 = 1.25;
281
282/// A due field whose meshes are prepared: what its update recomputes.
283struct FieldJob {
284    /// The field's index.
285    i: usize,
286    /// Every vertex is recomputed.
287    full: bool,
288    /// An update of the interaction tier (see [`Scene::assemble_live_pick`]):
289    /// the other vertices keep their values.
290    live: bool,
291    /// In such an update, the field's `LiveState::travel` after this update.
292    live_travel: f64,
293    /// Per target: it moved (see the module docs).
294    moved: Vec<bool>,
295    /// A's triangles changed since the last update.
296    topology: bool,
297    /// A's merged changes since the last update (`None`: everything).
298    changes: Option<ChangeSet>,
299    /// Per target: the margin of its near set, when it is needed.
300    near_margins: Vec<Option<f64>>,
301    /// Per target: from its local space into A's, as the pair queries take
302    /// it (unused when `scaled`).
303    b_to_a: Vec<Rigid>,
304    /// The most near pairs the near search may list (see
305    /// [`NEAR_PAIRS_PER_SLOT`]), decided here on the caller's thread.
306    near_limit: usize,
307    /// An estimate of the update's cost, in elements: every vertex slot of
308    /// A times the targets when all are recomputed; otherwise the known
309    /// candidates times the targets, plus A's triangle count for each near
310    /// search (which usually costs the most) whose target's root box is
311    /// within the margin of A's.
312    work: usize,
313    /// A's near triangles from a shared pair query with the reciprocal
314    /// field (single-target, same `max_distance`, both needing a near set):
315    /// used instead of running a second query.
316    shared_near: Option<Vec<TriangleId>>,
317    /// A scaled placement is involved (A's or a target's): candidates are
318    /// all vertices and [`Scene::compute_values_scaled`] measures them
319    /// through the placements (uniform scales by arithmetic, non-uniform
320    /// targets on baked copies in `temp_targets`).
321    scaled: bool,
322    /// Frame distances times this are world distances: A's uniform scale
323    /// when the frame is A's local space, 1 when it is the world.
324    frame_scale: f64,
325    /// Per target: the baked copy in the frame with its BVH and sign data,
326    /// for non-uniformly scaled targets (`None`: the real target is used).
327    temp_targets: Vec<Option<TempTarget>>,
328}
329
330/// A non-uniformly scaled target baked into the update's frame (see
331/// [`FieldJob::temp_targets`]).
332struct TempTarget {
333    mesh: Mesh,
334    bvh: Bvh,
335    sign: SignData,
336}
337
338impl FieldJob {
339    /// Some target moved.
340    fn geometric(&self) -> bool {
341        self.moved.contains(&true)
342    }
343}
344
345/// The new state of a field, computed without writing anything.
346struct FieldPlan {
347    /// In the interaction tier: the band of the next update.
348    margin: f64,
349    /// The vertices recomputed, ascending.
350    candidates: Vec<VertexId>,
351    /// The new value of every candidate, in candidate order.
352    values: Vec<f32>,
353    /// The target each new value came from, in candidate order.
354    nearest: Vec<u32>,
355    /// The candidates whose value bits changed, ascending.
356    changed: Vec<VertexId>,
357    /// The new "alive and used by a triangle" flags, when recomputed.
358    referenced: Option<Vec<bool>>,
359    /// The vertices with finite values, ascending.
360    finite: Vec<VertexId>,
361    /// Points the kernel ran on.
362    #[cfg_attr(not(test), allow(dead_code))]
363    recomputed: usize,
364    /// The near search passed its limit, and every vertex was recomputed.
365    #[cfg_attr(not(test), allow(dead_code))]
366    aborted: bool,
367}
368
369/// The vertices a field's update recomputes, chosen without writing
370/// anything (see the module docs).
371#[derive(Default)]
372struct Pick {
373    /// The vertices to recompute, ascending.
374    candidates: Vec<VertexId>,
375    /// Every vertex is recomputed.
376    all: bool,
377    /// The near search passed its limit.
378    aborted: bool,
379    /// In the interaction tier: the band of the next update.
380    margin: f64,
381}
382
383/// What is left of one step's budget (see
384/// [`Scene::field_step`](crate::Scene::field_step)).
385struct Step {
386    /// The step's budget, in points.
387    points: usize,
388    /// Values left to compute.
389    left: usize,
390    /// A sign data build, a near search or a walk over every vertex slot
391    /// ran: the step does nothing else.
392    worked: bool,
393}
394
395impl Step {
396    /// Claims the step for one long piece of work: `false` when the step
397    /// already did any work.
398    fn begin_heavy(&mut self) -> bool {
399        if self.unbounded() {
400            return true;
401        }
402        if self.worked || self.left < self.points {
403            return false;
404        }
405        self.worked = true;
406        true
407    }
408
409    /// An unbounded budget (`usize::MAX` points): the step runs the unit to
410    /// the end, long pieces of work included.
411    fn unbounded(&self) -> bool {
412        self.points == usize::MAX
413    }
414}
415
416/// How much one [`Scene::field_step`](crate::Scene::field_step) computes.
417#[derive(Debug, Clone, Copy, PartialEq, Eq)]
418#[non_exhaustive]
419pub enum FieldBudget {
420    /// Up to this many candidate vertices (at least one), counted across
421    /// the unit's fields in field order.
422    Points(usize),
423}
424
425/// Where the field step unit stands after
426/// [`Scene::field_step`](crate::Scene::field_step).
427#[derive(Debug, Clone, Copy, PartialEq, Eq)]
428#[non_exhaustive]
429pub enum StepState {
430    /// No Eager field was due, so no unit was opened.
431    Idle,
432    /// A unit is open, with candidates left to compute.
433    Pending,
434    /// This step computed the unit's last candidates and applied it.
435    Done,
436}
437
438/// Where a field of an open step unit stands. A field goes through every
439/// stage in order; those with nothing to do pass at once.
440#[derive(Debug, Clone, Copy, PartialEq, Eq)]
441enum Stage {
442    /// Building the sign data of target `k` and the later ones that are not
443    /// current.
444    Sign(usize),
445    /// Searching A's triangles near target `k` and the later moved ones.
446    Near(usize),
447    /// Choosing the candidates from the near triangles.
448    Pick,
449    /// Computing the candidates' values.
450    Values,
451    /// Computing what changes with them (the field's plan).
452    Finish,
453}
454
455/// A field of an open step unit.
456struct UnitField {
457    job: FieldJob,
458    stage: Stage,
459    /// The near search under way, in [`Stage::Near`].
460    search: Option<NearSearch>,
461    /// A's triangles near the moved targets searched so far.
462    near: Vec<TriangleId>,
463    /// Every vertex is recomputed (a full update, or a near search passed
464    /// its limit).
465    all: bool,
466    /// A near search passed its limit.
467    aborted: bool,
468    /// The candidates, from [`Stage::Pick`] on; taken by `plan` once every
469    /// value is computed.
470    pick: Pick,
471    /// The values of the candidates computed so far, in candidate order.
472    values: Vec<f32>,
473    /// The targets those values came from.
474    nearest: Vec<u32>,
475    /// Points the kernel ran on so far.
476    recomputed: usize,
477    /// The field's new state, once every value is computed; stays `None`
478    /// for a field whose computation failed, which is then not applied.
479    plan: Option<FieldPlan>,
480}
481
482/// The fields of a step unit (see
483/// [`Scene::field_step`](crate::Scene::field_step)), stepped as one unit:
484/// the Eager fields due at one state of the scene, or one field. It holds
485/// field indices and computed values only, so any change to the scene must
486/// drop it first.
487pub(crate) struct FieldUnit {
488    /// The one field the unit is for, when opened by
489    /// [`Scene::field_step_field`](crate::Scene::field_step_field).
490    only: Option<FieldId>,
491    fields: Vec<UnitField>,
492    /// The first field not finished yet.
493    next: usize,
494    /// Every field is finished and the unit applies on the next step (a
495    /// large unit applies in a step of its own).
496    finished: bool,
497    /// The error of the first field, in field order, that failed (when the
498    /// unit opened or since), with that field's index.
499    error: Option<(usize, SceneError)>,
500}
501
502impl FieldUnit {
503    /// Records the error of field `i`, keeping the first in field order.
504    fn fail(&mut self, i: usize, e: SceneError) {
505        if self.error.as_ref().is_none_or(|(j, _)| i < *j) {
506            self.error = Some((i, e));
507        }
508    }
509}
510
511/// What happened to one mesh of a field since the field's last update.
512#[derive(Debug, Clone)]
513pub(crate) struct FieldHook {
514    pub(crate) field: FieldId,
515    /// Merged changes; `None` means "everything".
516    pub(crate) changes: Option<ChangeSet>,
517    pub(crate) placement_changed: bool,
518    /// On a target: edits were reset from `changes` while the target was baked
519    /// (see `FieldJob::temp_targets`), which its sign data never saw. They
520    /// cannot be refitted from `changes` then: the next update builds them.
521    pub(crate) sign_missed_edits: bool,
522}
523
524impl FieldHook {
525    fn new(field: FieldId) -> FieldHook {
526        FieldHook {
527            field,
528            changes: Some(ChangeSet::default()),
529            placement_changed: false,
530            sign_missed_edits: false,
531        }
532    }
533
534    /// The changes to refit the target's sign data from, when they hold every
535    /// edit since those were last current.
536    fn refit_changes(&self) -> Option<&ChangeSet> {
537        self.changes.as_ref().filter(|_| !self.sign_missed_edits)
538    }
539
540    fn is_clean(&self) -> bool {
541        !self.placement_changed && self.changes.as_ref().is_some_and(ChangeSet::is_empty)
542    }
543}
544
545/// A mesh's colouring with its colours, one per vertex slot.
546pub(crate) struct ColorState {
547    coloring: Coloring,
548    /// The field's channel on the mesh.
549    channel: String,
550    colors: Vec<[u8; 4]>,
551}
552
553impl ColorState {
554    /// Recolours the vertices in `changed` (ascending) from `values`, and
555    /// colours slots added since the last call. Returns the recoloured ids,
556    /// ascending without duplicates.
557    fn update(&mut self, values: &[f32], changed: &[VertexId]) -> Vec<VertexId> {
558        let rule = &self.coloring.rule;
559        let old_len = self.colors.len();
560        let mut dirty = Vec::with_capacity(changed.len());
561        for &v in changed {
562            if (v as usize) < old_len {
563                self.colors[v as usize] = color_of(rule, values[v as usize]);
564                dirty.push(v);
565            }
566        }
567        for (v, &x) in values.iter().enumerate().skip(old_len) {
568            self.colors.push(color_of(rule, x));
569            dirty.push(v as VertexId);
570        }
571        dirty
572    }
573}
574
575/// A registered distance field.
576pub(crate) struct Field {
577    pub(crate) id: FieldId,
578    pub(crate) desc: DistanceField,
579    /// `field:<name>`, the channel on mesh `on`.
580    channel: String,
581    /// Each target's sign data, rebuilt when not current; `None` until first
582    /// built (see [`Scene::add_distance_field_deferred`]).
583    sign: Vec<Option<SignData>>,
584    /// Per target: a sign data build a step unit left unfinished, resumed
585    /// while its mesh and BVH are unchanged.
586    sign_builds: Vec<Option<SignBuild>>,
587    /// Applied at least once: [`Scene::withdraw_field`] no longer applies.
588    computed: bool,
589    /// Per vertex slot of A as of the last update: the index of the target
590    /// its value came from, `u32::MAX` for none. Shorter than the slots
591    /// when slots were added since; remapped with A by a compaction.
592    pub(crate) nearest: Vec<u32>,
593    /// Not computed yet: the next update computes every vertex.
594    fresh: bool,
595    /// The vertices with finite values, ascending.
596    finite: Vec<VertexId>,
597    /// Per vertex slot of A at the last update: alive and used by a triangle.
598    referenced: Vec<bool>,
599    /// What the interaction tier keeps (see [`Scene::set_field_interaction`]).
600    live: LiveState,
601}
602
603/// A field's state in the interaction tier.
604#[derive(Default)]
605struct LiveState {
606    /// An update of the tier left values out of date: the end of the
607    /// interaction recomputes every vertex.
608    stale: bool,
609    /// The band of the near vertices, in distance units; 0 until the first
610    /// update.
611    margin: f64,
612    /// The next vertex slot of the sweep (see [`LIVE_SWEEP_SHARE`]).
613    sweep: u32,
614    /// How far, at most, any vertex of A has moved relative to the targets
615    /// over the tier's updates (the sum of their moves).
616    travel: f64,
617    /// Per vertex slot of A: `travel` when its value was last computed.
618    stamps: Vec<f32>,
619    /// Per target: the `b_to_a` of the last update.
620    poses: Vec<Rigid>,
621}
622
623impl Field {
624    /// What the latest sign data build of target `k` found out, or the
625    /// placeholder of [`Scene::field_target_status`] before the first.
626    fn sign_status(&self, k: usize) -> SignStatus {
627        self.sign[k].as_ref().map_or(
628            SignStatus {
629                mode: match self.desc.sign {
630                    SignMode::Winding => SignMode::Winding,
631                    SignMode::Pseudonormal | SignMode::Auto => SignMode::Pseudonormal,
632                },
633                closed: false,
634                consistent_orientation: false,
635                non_manifold_vertices: 0,
636                degenerate_triangles: 0,
637                signed_volume: 0.0,
638            },
639            SignData::status,
640        )
641    }
642}
643
644/// A registered thickness field: its values are recomputed in full on every
645/// update (no targets, no sign data, no near sets), through
646/// [`Scene::update_thick_due`].
647pub(crate) struct ThickField {
648    pub(crate) id: FieldId,
649    pub(crate) desc: ThicknessField,
650    /// `field:<name>`, the channel on mesh `on`.
651    channel: String,
652    /// Applied at least once: [`Scene::withdraw_field`] no longer applies.
653    computed: bool,
654    /// Not computed yet: the next update recomputes every vertex.
655    fresh: bool,
656}
657
658/// A registered curvature field: its values are recomputed in full on every
659/// update (no targets, no sign data, no near sets), through
660/// [`Scene::update_curv_due`].
661pub(crate) struct CurvField {
662    pub(crate) id: FieldId,
663    pub(crate) desc: CurvatureField,
664    /// `field:<name>`, the channel on mesh `on`.
665    channel: String,
666    /// Applied at least once: [`Scene::withdraw_field`] no longer applies.
667    computed: bool,
668    /// Not computed yet: the next update recomputes every vertex.
669    fresh: bool,
670}
671
672/// Which registry a field id lives in.
673#[derive(Debug, Clone, Copy, PartialEq, Eq)]
674enum FieldRef {
675    Distance(usize),
676    Thickness(usize),
677    Curvature(usize),
678}
679
680impl Entry {
681    fn hook(&self, id: FieldId) -> Option<&FieldHook> {
682        self.hooks.iter().find(|h| h.field == id)
683    }
684
685    /// Empties the field's hook. With `baked` (a target its sign data were not
686    /// used for in this update), it remembers any edits those data missed.
687    fn reset_hook(&mut self, id: FieldId, baked: bool) {
688        if let Some(h) = self.hooks.iter_mut().find(|h| h.field == id) {
689            let missed =
690                baked && (h.sign_missed_edits || h.changes.as_ref().is_none_or(|c| !c.is_empty()));
691            *h = FieldHook::new(id);
692            h.sign_missed_edits = missed;
693        }
694    }
695
696    /// Colours vertex slots added since the colours were last sized, and the
697    /// reused slots among `added` (the store resets their field values to
698    /// NaN), from the field's channel (NaN for slots the field has not
699    /// computed), and reports them: the colours always cover every vertex
700    /// slot, and the channel never changes at an unreported vertex.
701    pub(crate) fn grow_colors(&mut self, added: &[VertexId]) {
702        let slots = self.mesh.vertex_slots();
703        let Some(cs) = &mut self.coloring else {
704            return;
705        };
706        let old = cs.colors.len();
707        let rule = &cs.coloring.rule;
708        let values = self.mesh.channel(&cs.channel).unwrap_or(&[]);
709        let color = |v: usize| values.get(v).map_or(rule.nan, |&x| color_of(rule, x));
710        let mut dirty: Vec<VertexId> = added
711            .iter()
712            .copied()
713            .filter(|&v| (v as usize) < old)
714            .collect();
715        for &v in &dirty {
716            cs.colors[v as usize] = color(v as usize);
717        }
718        if slots > old {
719            cs.colors.extend((old..slots).map(color));
720            dirty.extend(old as VertexId..slots as VertexId);
721        }
722        if dirty.is_empty() {
723            return;
724        }
725        dirty.sort_unstable();
726        dirty.dedup();
727        self.report_colors(Some(&dirty));
728    }
729
730    /// Recolours every vertex from the current field values (after a new
731    /// colouring or a compaction). Missing values colour as NaN.
732    pub(crate) fn recolor_all(&mut self) {
733        if let Some(cs) = &mut self.coloring {
734            let rule = &cs.coloring.rule;
735            cs.colors = match self.mesh.channel(&cs.channel) {
736                Some(values) => values.iter().map(|&x| color_of(rule, x)).collect(),
737                None => vec![rule.nan; self.mesh.vertex_slots()],
738            };
739        }
740    }
741}
742
743impl Scene {
744    /// Registers a signed distance field on mesh `desc.on`, measured to the
745    /// meshes `desc.to`, and returns its id. Its values go into a derived
746    /// channel `field:<name>` on `desc.on`, readable with
747    /// [`Scene::field_values`] or through [`Scene::mesh`]; they are not
748    /// undoable. With several targets, a vertex's value is the smallest over
749    /// them, and [`Scene::field_nearest`] tells which target it came from.
750    ///
751    /// The field follows the [`UpdateMode`] of `desc.on`: an Eager field is
752    /// computed right away and after every edit, placement change, undo,
753    /// redo or cancel touching `desc.on` or a target; a Lazy one by
754    /// [`Scene::flush`] or [`Scene::update_fields`]; a Manual one only by
755    /// [`Scene::update_fields`]. Until then its values are NaN.
756    ///
757    /// The targets' BVHs are brought up to date under the query rules (a
758    /// Lazy one is updated) and their sign data built here, so that
759    /// [`Scene::field_status`] can report their topology at once.
760    ///
761    /// A position-only edit of a target mesh refits its pseudonormal sign
762    /// data around the dirty vertices; any other edit rebuilds it over the
763    /// whole mesh (O(n), about 150 ms at 1M triangles, more after a topology
764    /// edit, which drops the cached adjacency). Placement changes and edits
765    /// of `desc.on` do not rebuild it.
766    ///
767    /// # Errors
768    /// [`SceneError::UnknownMesh`]; [`SceneError::InvalidInput`] when `to` is
769    /// empty, holds `on` or a mesh twice, `max_distance` is negative or NaN,
770    /// or `on` already has a field of that name; [`SceneError::StaleBvh`]
771    /// when a target is a stale Manual mesh; a BVH, field or memory error.
772    /// The scene is unchanged on error.
773    pub fn add_distance_field(&mut self, desc: DistanceField) -> Result<FieldId> {
774        self.drop_field_unit();
775        let (id, eager) = self.register_field(desc, true)?;
776        if eager {
777            let _ = self.update_field(self.fields.len() - 1);
778        }
779        Ok(id)
780    }
781
782    /// Registers a field as [`Scene::add_distance_field`] does, but computes
783    /// nothing yet: no sign data are built and no BVH is brought up to date,
784    /// whatever the mode of `desc.on`. The field is due until an update
785    /// computes it: [`Scene::field_step`] (an Eager field),
786    /// [`Scene::field_step_field`], [`Scene::update_fields`],
787    /// [`Scene::flush`] (Eager and Lazy) or, unless deferred, the next
788    /// change to one of its meshes (Eager). Until then its values are NaN
789    /// and [`Scene::field_status`] reports placeholder sign data (see
790    /// [`Scene::field_target_status`]). Computed in steps, its values,
791    /// colours and reports are the same, bit for bit, as from
792    /// [`Scene::add_distance_field`].
793    ///
794    /// # Errors
795    /// [`SceneError::UnknownMesh`]; [`SceneError::InvalidInput`] as for
796    /// [`Scene::add_distance_field`]; a memory error. The scene is unchanged
797    /// on error. Errors of the BVHs and sign data come from the update that
798    /// computes the field.
799    pub fn add_distance_field_deferred(&mut self, desc: DistanceField) -> Result<FieldId> {
800        self.drop_field_unit();
801        Ok(self.register_field(desc, false)?.0)
802    }
803
804    /// Removes a field that has never been computed, as if it had never
805    /// been added: unlike [`Scene::remove_field`] nothing is reported.
806    /// Returns `false`, doing nothing, when the field was computed (by any
807    /// update) or colours its mesh; [`Scene::remove_field`] then applies.
808    ///
809    /// # Errors
810    /// [`SceneError::UnknownField`] for an unknown or removed field.
811    pub fn withdraw_field(&mut self, id: FieldId) -> Result<bool> {
812        self.drop_field_unit();
813        match self.field_ref(id)? {
814            FieldRef::Distance(i) => {
815                let f = &self.fields[i];
816                let colours = self
817                    .entry(f.desc.on)
818                    .ok()
819                    .and_then(|e| e.coloring.as_ref())
820                    .is_some_and(|c| c.coloring.field == id);
821                if f.computed || colours {
822                    return Ok(false);
823                }
824                let f = self.fields.remove(i);
825                for &m in std::iter::once(&f.desc.on).chain(&f.desc.to) {
826                    if let Some(e) = self.entries.get_mut(m) {
827                        e.hooks.retain(|h| h.field != id);
828                    }
829                }
830                if let Some(ea) = self.entries.get_mut(f.desc.on) {
831                    let _ = ea.mesh.remove_channel(&f.channel);
832                }
833                Ok(true)
834            }
835            FieldRef::Thickness(t) => {
836                let f = &self.thick[t];
837                let colours = self
838                    .entry(f.desc.on)
839                    .ok()
840                    .and_then(|e| e.coloring.as_ref())
841                    .is_some_and(|c| c.coloring.field == id);
842                if f.computed || colours {
843                    return Ok(false);
844                }
845                let f = self.thick.remove(t);
846                if let Some(ea) = self.entries.get_mut(f.desc.on) {
847                    ea.hooks.retain(|h| h.field != id);
848                    let _ = ea.mesh.remove_channel(&f.channel);
849                }
850                Ok(true)
851            }
852            FieldRef::Curvature(c) => {
853                let f = &self.curv[c];
854                let colours = self
855                    .entry(f.desc.on)
856                    .ok()
857                    .and_then(|e| e.coloring.as_ref())
858                    .is_some_and(|c| c.coloring.field == id);
859                if f.computed || colours {
860                    return Ok(false);
861                }
862                let f = self.curv.remove(c);
863                if let Some(ea) = self.entries.get_mut(f.desc.on) {
864                    ea.hooks.retain(|h| h.field != id);
865                    let _ = ea.mesh.remove_channel(&f.channel);
866                }
867                Ok(true)
868            }
869        }
870    }
871
872    /// Validates `desc` and registers its field (its channel, hooks and,
873    /// with `sign`, its targets' sign data, bringing their BVHs up to date
874    /// first). Returns its id and whether its mesh is Eager. All or nothing.
875    fn register_field(&mut self, desc: DistanceField, sign: bool) -> Result<(FieldId, bool)> {
876        self.entry(desc.on)?;
877        for &to in &desc.to {
878            self.entry(to)?;
879        }
880        if desc.to.is_empty() {
881            return Err(SceneError::InvalidInput("a field needs a target mesh"));
882        }
883        if desc.to.contains(&desc.on) {
884            return Err(SceneError::InvalidInput(
885                "a field needs two different meshes",
886            ));
887        }
888        if (1..desc.to.len()).any(|k| desc.to[..k].contains(&desc.to[k])) {
889            return Err(SceneError::InvalidInput(
890                "a field's target meshes must all differ",
891            ));
892        }
893        if desc.max_distance.is_nan() || desc.max_distance < 0.0 {
894            return Err(SceneError::InvalidInput(
895                "max_distance must be zero or greater, and not NaN",
896            ));
897        }
898        if self
899            .fields
900            .iter()
901            .any(|f| f.desc.on == desc.on && f.desc.name == desc.name)
902            || self
903                .thick
904                .iter()
905                .any(|f| f.desc.on == desc.on && f.desc.name == desc.name)
906            || self
907                .curv
908                .iter()
909                .any(|f| f.desc.on == desc.on && f.desc.name == desc.name)
910        {
911            return Err(SceneError::InvalidInput(
912                "the mesh already has a field of that name",
913            ));
914        }
915        let next = self
916            .next_field
917            .checked_add(1)
918            .ok_or(SceneError::InvalidInput("too many fields"))?;
919        let mut signs = Vec::new();
920        signs.try_reserve_exact(desc.to.len()).map_err(|_| oom())?;
921        let mut sign_builds = Vec::new();
922        sign_builds
923            .try_reserve_exact(desc.to.len())
924            .map_err(|_| oom())?;
925        if sign {
926            self.prepare(Some(&desc.to))?;
927        }
928        for &to in &desc.to {
929            let eb = self.entries.get_mut(to).ok_or(SceneError::UnknownMesh)?;
930            signs.push(if sign {
931                Some(SignData::build(&mut eb.mesh, &eb.bvh, desc.sign)?)
932            } else {
933                None
934            });
935            sign_builds.push(None);
936            eb.hooks.try_reserve(1).map_err(|_| oom())?;
937        }
938        self.fields.try_reserve(1).map_err(|_| oom())?;
939        let channel = format!("{FIELD_PREFIX}{}", desc.name);
940        let ea = self
941            .entries
942            .get_mut(desc.on)
943            .ok_or(SceneError::UnknownMesh)?;
944        ea.hooks.try_reserve(1).map_err(|_| oom())?;
945        ea.mesh
946            .add_channel(ChannelDesc::derived(channel.clone(), 1))?;
947        // Nothing can fail from here on.
948        let id = FieldId(self.next_field);
949        self.next_field = next;
950        ea.hooks.push(FieldHook::new(id));
951        let eager = ea.mode == UpdateMode::Eager;
952        for &to in &desc.to {
953            if let Some(eb) = self.entries.get_mut(to) {
954                eb.hooks.push(FieldHook::new(id));
955            }
956        }
957        self.fields.push(Field {
958            id,
959            desc,
960            channel,
961            sign: signs,
962            sign_builds,
963            computed: false,
964            nearest: Vec::new(),
965            fresh: true,
966            finite: Vec::new(),
967            referenced: Vec::new(),
968            live: LiveState::default(),
969        });
970        Ok((id, eager))
971    }
972
973    /// Registers a per-vertex wall-thickness field on mesh `desc.on`, and
974    /// returns its id. Its values go into a derived channel `field:<name>` on
975    /// `desc.on`, readable with [`Scene::field_values`] or through
976    /// [`Scene::mesh`]; they are not undoable.
977    ///
978    /// The field follows the [`UpdateMode`] of `desc.on` like a distance
979    /// field: an Eager one is computed right away and after every edit,
980    /// placement change, undo, redo or cancel touching `desc.on`; a Lazy one
981    /// by [`Scene::flush`] or [`Scene::update_fields`]; a Manual one only by
982    /// [`Scene::update_fields`]. Until then its values are NaN. Every update
983    /// recomputes every vertex in full (a thickness has no targets to narrow
984    /// it down to).
985    ///
986    /// A non-uniformly scaled placement is baked into a world-frame copy for
987    /// the update, so the values are world distances; a uniform scale
988    /// multiplies the local values, and a rigid placement changes nothing.
989    ///
990    /// # Errors
991    /// [`SceneError::UnknownMesh`]; [`SceneError::InvalidInput`] when
992    /// `max_distance` is negative or NaN, or `on` already has a field of that
993    /// name; [`SceneError::StaleBvh`] when the mesh is a stale Manual mesh; a
994    /// BVH, field or memory error. The scene is unchanged on error.
995    pub fn add_thickness_field(&mut self, desc: ThicknessField) -> Result<FieldId> {
996        self.drop_field_unit();
997        let (id, eager) = self.register_thickness(desc)?;
998        if eager {
999            let _ = self.update_thick_field(self.thick.len() - 1);
1000        }
1001        Ok(id)
1002    }
1003
1004    /// [`Scene::add_thickness_field`] computing nothing yet: the field is due
1005    /// until an update computes it ([`Scene::field_step`] (an Eager field),
1006    /// [`Scene::field_step_field`], [`Scene::update_fields`] or
1007    /// [`Scene::flush`], unless deferred, the next change to its mesh (an
1008    /// Eager field)). Until then its values are NaN.
1009    ///
1010    /// # Errors
1011    /// [`SceneError::UnknownMesh`]; [`SceneError::InvalidInput`] as for
1012    /// [`Scene::add_thickness_field`]; a memory error. The scene is unchanged
1013    /// on error. Errors of the BVH come from the update that computes the
1014    /// field.
1015    pub fn add_thickness_field_deferred(&mut self, desc: ThicknessField) -> Result<FieldId> {
1016        self.drop_field_unit();
1017        Ok(self.register_thickness(desc)?.0)
1018    }
1019
1020    /// Validates `desc` and registers its thickness field (its channel and
1021    /// hook). Returns its id and whether its mesh is Eager. All or nothing.
1022    fn register_thickness(&mut self, desc: ThicknessField) -> Result<(FieldId, bool)> {
1023        self.entry(desc.on)?;
1024        if desc.max_distance.is_nan() || desc.max_distance < 0.0 {
1025            return Err(SceneError::InvalidInput(
1026                "max_distance must be zero or greater, and not NaN",
1027            ));
1028        }
1029        if self
1030            .fields
1031            .iter()
1032            .any(|f| f.desc.on == desc.on && f.desc.name == desc.name)
1033            || self
1034                .thick
1035                .iter()
1036                .any(|f| f.desc.on == desc.on && f.desc.name == desc.name)
1037            || self
1038                .curv
1039                .iter()
1040                .any(|f| f.desc.on == desc.on && f.desc.name == desc.name)
1041        {
1042            return Err(SceneError::InvalidInput(
1043                "the mesh already has a field of that name",
1044            ));
1045        }
1046        let next = self
1047            .next_field
1048            .checked_add(1)
1049            .ok_or(SceneError::InvalidInput("too many fields"))?;
1050        let channel = format!("{FIELD_PREFIX}{}", desc.name);
1051        let ea = self
1052            .entries
1053            .get_mut(desc.on)
1054            .ok_or(SceneError::UnknownMesh)?;
1055        ea.hooks.try_reserve(1).map_err(|_| oom())?;
1056        ea.mesh
1057            .add_channel(ChannelDesc::derived(channel.clone(), 1))?;
1058        // Nothing can fail from here on.
1059        let id = FieldId(self.next_field);
1060        self.next_field = next;
1061        ea.hooks.push(FieldHook::new(id));
1062        let eager = ea.mode == UpdateMode::Eager;
1063        self.thick.try_reserve(1).map_err(|_| oom())?;
1064        self.thick.push(ThickField {
1065            id,
1066            desc,
1067            channel,
1068            computed: false,
1069            fresh: true,
1070        });
1071        Ok((id, eager))
1072    }
1073
1074    /// Registers a per-vertex curvature field on mesh `desc.on`, and returns
1075    /// its id. Its values go into a derived channel `field:<name>` on
1076    /// `desc.on`, readable with [`Scene::field_values`] or through
1077    /// [`Scene::mesh`]; they are not undoable.
1078    ///
1079    /// The field follows the [`UpdateMode`] of `desc.on` like a distance
1080    /// field: an Eager one is computed right away and after every edit,
1081    /// placement change, undo, redo or cancel touching `desc.on`; a Lazy one
1082    /// by [`Scene::flush`] or [`Scene::update_fields`]; a Manual one only by
1083    /// [`Scene::update_fields`]. Until then its values are NaN. Every update
1084    /// recomputes every vertex in full (a curvature has no targets to narrow
1085    /// it down to).
1086    ///
1087    /// A non-uniformly scaled placement is baked into a world-frame copy for
1088    /// the update, so the values are world curvatures; a uniform scale `s`
1089    /// divides the local values by `s`, and a rigid placement changes
1090    /// nothing.
1091    ///
1092    /// # Errors
1093    /// [`SceneError::UnknownMesh`]; [`SceneError::InvalidInput`] when `on`
1094    /// already has a field of that name; [`SceneError::StaleBvh`] when the
1095    /// mesh is a stale Manual mesh; a BVH, field or memory error. The scene
1096    /// is unchanged on error.
1097    pub fn add_curvature_field(&mut self, desc: CurvatureField) -> Result<FieldId> {
1098        self.drop_field_unit();
1099        let (id, eager) = self.register_curvature(desc)?;
1100        if eager {
1101            let _ = self.update_curv_field(self.curv.len() - 1);
1102        }
1103        Ok(id)
1104    }
1105
1106    /// [`Scene::add_curvature_field`] computing nothing yet: the field is due
1107    /// until an update computes it ([`Scene::field_step`] (an Eager field),
1108    /// [`Scene::field_step_field`], [`Scene::update_fields`] or
1109    /// [`Scene::flush`], unless deferred, the next change to its mesh (an
1110    /// Eager field)). Until then its values are NaN.
1111    ///
1112    /// # Errors
1113    /// [`SceneError::UnknownMesh`]; [`SceneError::InvalidInput`] as for
1114    /// [`Scene::add_curvature_field`]; a memory error. The scene is unchanged
1115    /// on error. Errors of the BVH come from the update that computes the
1116    /// field.
1117    pub fn add_curvature_field_deferred(&mut self, desc: CurvatureField) -> Result<FieldId> {
1118        self.drop_field_unit();
1119        Ok(self.register_curvature(desc)?.0)
1120    }
1121
1122    /// Validates `desc` and registers its curvature field (its channel and
1123    /// hook). Returns its id and whether its mesh is Eager. All or nothing.
1124    fn register_curvature(&mut self, desc: CurvatureField) -> Result<(FieldId, bool)> {
1125        self.entry(desc.on)?;
1126        if self
1127            .fields
1128            .iter()
1129            .any(|f| f.desc.on == desc.on && f.desc.name == desc.name)
1130            || self
1131                .thick
1132                .iter()
1133                .any(|f| f.desc.on == desc.on && f.desc.name == desc.name)
1134            || self
1135                .curv
1136                .iter()
1137                .any(|f| f.desc.on == desc.on && f.desc.name == desc.name)
1138        {
1139            return Err(SceneError::InvalidInput(
1140                "the mesh already has a field of that name",
1141            ));
1142        }
1143        let next = self
1144            .next_field
1145            .checked_add(1)
1146            .ok_or(SceneError::InvalidInput("too many fields"))?;
1147        let channel = format!("{FIELD_PREFIX}{}", desc.name);
1148        let ea = self
1149            .entries
1150            .get_mut(desc.on)
1151            .ok_or(SceneError::UnknownMesh)?;
1152        ea.hooks.try_reserve(1).map_err(|_| oom())?;
1153        ea.mesh
1154            .add_channel(ChannelDesc::derived(channel.clone(), 1))?;
1155        // Nothing can fail from here on.
1156        let id = FieldId(self.next_field);
1157        self.next_field = next;
1158        ea.hooks.push(FieldHook::new(id));
1159        let eager = ea.mode == UpdateMode::Eager;
1160        self.curv.try_reserve(1).map_err(|_| oom())?;
1161        self.curv.push(CurvField {
1162            id,
1163            desc,
1164            channel,
1165            computed: false,
1166            fresh: true,
1167        });
1168        Ok((id, eager))
1169    }
1170
1171    /// Removes a field: its channel leaves its mesh, a colouring by it is
1172    /// removed (reported as `colors_dirty: None`), and the removal is
1173    /// reported in the mesh's [`MeshChanges::fields_changed`](crate::MeshChanges::fields_changed).
1174    /// The id is rejected from then on.
1175    ///
1176    /// # Errors
1177    /// [`SceneError::UnknownField`] for an unknown or removed field.
1178    pub fn remove_field(&mut self, id: FieldId) -> Result<()> {
1179        self.drop_field_unit();
1180        match self.field_ref(id)? {
1181            FieldRef::Distance(i) => self.detach_field(i),
1182            FieldRef::Thickness(t) => self.detach_thick(t),
1183            FieldRef::Curvature(c) => self.detach_curv(c),
1184        }
1185        Ok(())
1186    }
1187
1188    /// Brings every field that is not current up to date, whatever the mode
1189    /// of its mesh, bringing the BVHs it needs up to date under the query
1190    /// rules (Lazy ones are updated). Renderers call this (or
1191    /// [`Scene::flush`]), then read any number of fields and colours.
1192    ///
1193    /// # Errors
1194    /// The first error, for example [`SceneError::StaleBvh`] when a BVH a
1195    /// field needs is a stale Manual one: B's always, A's when the field
1196    /// must look for A's triangles near B (after a placement change or an
1197    /// edit of B). Such a field makes every call (and every
1198    /// [`Scene::flush`]) return `StaleBvh` until that BVH is brought up to
1199    /// date with [`Scene::update`]. Every field is still attempted; those
1200    /// that failed stay stale and keep their pending changes.
1201    pub fn update_fields(&mut self) -> Result<()> {
1202        self.drop_field_unit();
1203        self.run_fields(|_| true, None)
1204    }
1205
1206    /// The field's values, one per vertex slot of its mesh, as of its last
1207    /// update (see [`Scene::field_status`]): signed distances, `+∞` for far,
1208    /// NaN for dead or unused vertices. A thickness field holds wall
1209    /// thicknesses instead (`+∞` past `max_distance`, NaN where the rays
1210    /// escape), and a curvature field signed mean curvatures in 1/world-unit
1211    /// (NaN for dead or unused vertices). Never updates.
1212    ///
1213    /// # Errors
1214    /// [`SceneError::UnknownField`] for an unknown or removed field.
1215    pub fn field_values(&self, id: FieldId) -> Result<&[f32]> {
1216        let (on, channel) = match self.field_ref(id)? {
1217            FieldRef::Distance(i) => {
1218                let f = &self.fields[i];
1219                (f.desc.on, f.channel.as_str())
1220            }
1221            FieldRef::Thickness(t) => {
1222                let f = &self.thick[t];
1223                (f.desc.on, f.channel.as_str())
1224            }
1225            FieldRef::Curvature(c) => {
1226                let f = &self.curv[c];
1227                (f.desc.on, f.channel.as_str())
1228            }
1229        };
1230        self.entry(on)?
1231            .mesh
1232            .channel(channel)
1233            .ok_or(SceneError::Corrupted)
1234    }
1235
1236    /// Per vertex slot of the field's mesh, as of its last update: the index
1237    /// in [`Scene::field_targets`] of the target its value came from (the
1238    /// lowest on a tie), or `u32::MAX` for none (a `+∞` or NaN value, or a
1239    /// slot not computed yet). It can be shorter than the mesh's slots
1240    /// (slots added since the last update): those slots have none.
1241    /// Thickness and curvature fields have no targets and report none.
1242    /// Never updates.
1243    ///
1244    /// After a target mesh is removed, and until the field's next update
1245    /// (at once for an Eager field, unless deferred), the vertices whose
1246    /// value came from it have none here while their value stays finite, and
1247    /// the later indices are shifted down to the remaining targets.
1248    ///
1249    /// # Errors
1250    /// [`SceneError::UnknownField`] for an unknown or removed field.
1251    pub fn field_nearest(&self, id: FieldId) -> Result<&[u32]> {
1252        match self.field_ref(id)? {
1253            FieldRef::Distance(i) => Ok(&self.fields[i].nearest),
1254            FieldRef::Thickness(_) => Ok(&[]),
1255            FieldRef::Curvature(_) => Ok(&[]),
1256        }
1257    }
1258
1259    /// The field's target meshes, in index order. Removing a target mesh
1260    /// removes it from here, shifting the later ones down. Thickness and
1261    /// curvature fields have no targets.
1262    ///
1263    /// # Errors
1264    /// [`SceneError::UnknownField`] for an unknown or removed field.
1265    pub fn field_targets(&self, id: FieldId) -> Result<&[MeshId]> {
1266        match self.field_ref(id)? {
1267            FieldRef::Distance(i) => Ok(&self.fields[i].desc.to),
1268            FieldRef::Thickness(_) => Ok(&[]),
1269            FieldRef::Curvature(_) => Ok(&[]),
1270        }
1271    }
1272
1273    /// What the latest sign data build found out about each of the field's
1274    /// targets, in index order. Thickness and curvature fields have no
1275    /// targets. Never updates.
1276    ///
1277    /// # Errors
1278    /// [`SceneError::UnknownField`] for an unknown or removed field.
1279    /// A target whose sign data were never built (a field added with
1280    /// [`Scene::add_distance_field_deferred`], not computed yet) reports a
1281    /// placeholder: the requested mode (`Auto` as
1282    /// [`SignMode::Pseudonormal`]), not closed, not consistently oriented,
1283    /// and no counts.
1284    pub fn field_target_status(&self, id: FieldId) -> Result<Vec<SignStatus>> {
1285        match self.field_ref(id)? {
1286            FieldRef::Distance(i) => {
1287                let f = &self.fields[i];
1288                Ok((0..f.sign.len()).map(|k| f.sign_status(k)).collect())
1289            }
1290            FieldRef::Thickness(_) => Ok(Vec::new()),
1291            FieldRef::Curvature(_) => Ok(Vec::new()),
1292        }
1293    }
1294
1295    /// Whether the field is current, and what the latest sign data builds
1296    /// found out about its target meshes, together (see
1297    /// [`FieldStatus::sign`]). Thickness and curvature fields have no sign
1298    /// data and report the placeholder (neither closed nor consistently
1299    /// oriented). Never updates.
1300    ///
1301    /// # Errors
1302    /// [`SceneError::UnknownField`] for an unknown or removed field.
1303    pub fn field_status(&self, id: FieldId) -> Result<FieldStatus> {
1304        match self.field_ref(id)? {
1305            FieldRef::Distance(i) => {
1306                let f = &self.fields[i];
1307                let mut sign = f.sign_status(0);
1308                for k in 1..f.sign.len() {
1309                    let s = f.sign_status(k);
1310                    sign.closed &= s.closed;
1311                    sign.consistent_orientation &= s.consistent_orientation;
1312                    sign.non_manifold_vertices += s.non_manifold_vertices;
1313                    sign.degenerate_triangles += s.degenerate_triangles;
1314                    sign.signed_volume += s.signed_volume;
1315                }
1316                Ok(FieldStatus {
1317                    current: self.field_current(i),
1318                    sign,
1319                })
1320            }
1321            FieldRef::Thickness(t) => Ok(FieldStatus {
1322                current: self.thick_current(t),
1323                sign: SignStatus {
1324                    mode: SignMode::Pseudonormal,
1325                    closed: false,
1326                    consistent_orientation: false,
1327                    non_manifold_vertices: 0,
1328                    degenerate_triangles: 0,
1329                    signed_volume: 0.0,
1330                },
1331            }),
1332            FieldRef::Curvature(c) => Ok(FieldStatus {
1333                current: self.curv_current(c),
1334                sign: SignStatus {
1335                    mode: SignMode::Pseudonormal,
1336                    closed: false,
1337                    consistent_orientation: false,
1338                    non_manifold_vertices: 0,
1339                    degenerate_triangles: 0,
1340                    signed_volume: 0.0,
1341                },
1342            }),
1343        }
1344    }
1345
1346    /// Colours `mesh` from one of its fields, or stops colouring it with
1347    /// `None`. Every vertex is recoloured from the field's current values,
1348    /// and reported as `colors_dirty: None`. Field updates then recolour the
1349    /// vertices whose values changed and report them. Colours are not
1350    /// undoable.
1351    ///
1352    /// # Errors
1353    /// [`SceneError::UnknownMesh`]; [`SceneError::UnknownField`];
1354    /// [`SceneError::InvalidInput`] when the field is not on `mesh` or the
1355    /// rule is invalid (see [`ColorRule::validate`]). The scene is unchanged
1356    /// on error.
1357    pub fn set_coloring(&mut self, mesh: MeshId, coloring: Option<Coloring>) -> Result<()> {
1358        self.drop_field_unit();
1359        self.entry(mesh)?;
1360        let state = match coloring {
1361            None => None,
1362            Some(c) => {
1363                let (on, channel) = match self.field_ref(c.field)? {
1364                    FieldRef::Distance(i) => {
1365                        let f = &self.fields[i];
1366                        (f.desc.on, f.channel.clone())
1367                    }
1368                    FieldRef::Thickness(t) => {
1369                        let f = &self.thick[t];
1370                        (f.desc.on, f.channel.clone())
1371                    }
1372                    FieldRef::Curvature(c) => {
1373                        let f = &self.curv[c];
1374                        (f.desc.on, f.channel.clone())
1375                    }
1376                };
1377                if on != mesh {
1378                    return Err(SceneError::InvalidInput("the field is not on this mesh"));
1379                }
1380                c.rule.validate().map_err(|e| match e {
1381                    FieldsError::InvalidInput(m) => SceneError::InvalidInput(m),
1382                    e => SceneError::Fields(e),
1383                })?;
1384                Some(ColorState {
1385                    coloring: c,
1386                    channel,
1387                    colors: Vec::new(),
1388                })
1389            }
1390        };
1391        let e = self.entries.get_mut(mesh).ok_or(SceneError::UnknownMesh)?;
1392        let had = e.coloring.is_some();
1393        e.coloring = state;
1394        e.recolor_all();
1395        if had || e.coloring.is_some() {
1396            e.report_colors(None);
1397        }
1398        Ok(())
1399    }
1400
1401    /// The mesh's colours, or `None` when the mesh has no colouring. Never
1402    /// updates.
1403    ///
1404    /// The slice always holds exactly one RGBA8 per vertex slot
1405    /// (`colors(mesh).len() == mesh.vertex_slots()`), so it can be indexed
1406    /// by vertex id at any time. The colours follow the field's values as of
1407    /// its last update; slots added since are coloured from the field's
1408    /// channel (NaN, so [`ColorRule::nan`]) until the field is updated.
1409    ///
1410    /// # Errors
1411    /// [`SceneError::UnknownMesh`] for a stale id.
1412    pub fn colors(&self, mesh: MeshId) -> Result<Option<&[[u8; 4]]>> {
1413        Ok(self
1414            .entry(mesh)?
1415            .coloring
1416            .as_ref()
1417            .map(|c| c.colors.as_slice()))
1418    }
1419
1420    /// The values the mesh's colours are made from, or `None` when the mesh
1421    /// has no colouring. Never updates.
1422    ///
1423    /// A view of the colouring field's channel, not a copy: the same slice as
1424    /// [`Scene::field_values`] of that field, one `f32` per vertex slot. It
1425    /// changes only at vertices reported in `colors_dirty`, so a renderer can
1426    /// mirror it with the colours' dirty ranges and map values to colours per
1427    /// fragment ([`color_of`] at a vertex gives that vertex's colour).
1428    ///
1429    /// # Errors
1430    /// [`SceneError::UnknownMesh`] for a stale id.
1431    pub fn color_values(&self, mesh: MeshId) -> Result<Option<&[f32]>> {
1432        let e = self.entry(mesh)?;
1433        Ok(e.coloring
1434            .as_ref()
1435            .map(|c| e.mesh.channel(&c.channel).unwrap_or(&[])))
1436    }
1437
1438    /// The mesh's colouring, or `None` when the mesh has no colouring. Never
1439    /// updates.
1440    ///
1441    /// # Errors
1442    /// [`SceneError::UnknownMesh`] for a stale id.
1443    pub fn coloring(&self, mesh: MeshId) -> Result<Option<&Coloring>> {
1444        Ok(self.entry(mesh)?.coloring.as_ref().map(|c| &c.coloring))
1445    }
1446
1447    /// The ids of the fields on `mesh`, in the order they were added. Never
1448    /// updates.
1449    ///
1450    /// # Errors
1451    /// [`SceneError::UnknownMesh`] for a stale id.
1452    pub fn fields_on(&self, mesh: MeshId) -> Result<Vec<FieldId>> {
1453        self.entry(mesh)?;
1454        Ok(self
1455            .fields
1456            .iter()
1457            .filter(|f| f.desc.on == mesh)
1458            .map(|f| f.id)
1459            .chain(
1460                self.thick
1461                    .iter()
1462                    .filter(|f| f.desc.on == mesh)
1463                    .map(|f| f.id),
1464            )
1465            .chain(self.curv.iter().filter(|f| f.desc.on == mesh).map(|f| f.id))
1466            .collect())
1467    }
1468
1469    /// Changes a field's `max_distance` and sign mode, keeping its id, its
1470    /// name, its meshes and any colouring by it. The field is recomputed in
1471    /// full on its next update (at once for an Eager field); until then its
1472    /// values are stale and [`Scene::field_status`] reports it as not
1473    /// current, with placeholder sign data when the sign mode changed. A
1474    /// thickness field has no sign mode (`sign` is ignored for it), and a
1475    /// curvature field has neither (`max_distance` is ignored for it too:
1476    /// the call still marks it for recompute).
1477    ///
1478    /// # Errors
1479    /// [`SceneError::UnknownField`] for an unknown or removed field;
1480    /// [`SceneError::InvalidInput`] when `max_distance` is negative or NaN.
1481    /// The scene is unchanged on error.
1482    pub fn reconfigure_field(
1483        &mut self,
1484        id: FieldId,
1485        max_distance: f64,
1486        sign: SignMode,
1487    ) -> Result<()> {
1488        self.drop_field_unit();
1489        if max_distance.is_nan() || max_distance < 0.0 {
1490            return Err(SceneError::InvalidInput(
1491                "max_distance must be zero or greater, and not NaN",
1492            ));
1493        }
1494        match self.field_ref(id)? {
1495            FieldRef::Distance(i) => {
1496                {
1497                    let f = &mut self.fields[i];
1498                    if f.desc.max_distance == max_distance && f.desc.sign == sign {
1499                        return Ok(());
1500                    }
1501                    f.desc.max_distance = max_distance;
1502                    if f.desc.sign != sign {
1503                        f.desc.sign = sign;
1504                        for s in f.sign.iter_mut() {
1505                            *s = None;
1506                        }
1507                        for b in f.sign_builds.iter_mut() {
1508                            *b = None;
1509                        }
1510                    }
1511                    f.fresh = true;
1512                }
1513                let on = self.fields[i].desc.on;
1514                let eager = self
1515                    .entry(on)
1516                    .map(|e| e.mode == UpdateMode::Eager)
1517                    .unwrap_or(false);
1518                if eager {
1519                    let _ = self.update_field(i);
1520                }
1521                Ok(())
1522            }
1523            FieldRef::Thickness(t) => {
1524                {
1525                    let f = &mut self.thick[t];
1526                    if f.desc.max_distance == max_distance {
1527                        return Ok(());
1528                    }
1529                    f.desc.max_distance = max_distance;
1530                    f.fresh = true;
1531                }
1532                let on = self.thick[t].desc.on;
1533                let eager = self
1534                    .entry(on)
1535                    .map(|e| e.mode == UpdateMode::Eager)
1536                    .unwrap_or(false);
1537                if eager {
1538                    let _ = self.update_thick_field(t);
1539                }
1540                Ok(())
1541            }
1542            FieldRef::Curvature(c) => {
1543                {
1544                    let f = &mut self.curv[c];
1545                    f.fresh = true;
1546                }
1547                let on = self.curv[c].desc.on;
1548                let eager = self
1549                    .entry(on)
1550                    .map(|e| e.mode == UpdateMode::Eager)
1551                    .unwrap_or(false);
1552                if eager {
1553                    let _ = self.update_curv_field(c);
1554                }
1555                Ok(())
1556            }
1557        }
1558    }
1559
1560    /// The id of the registered field with this [`FieldId::raw`] value, or
1561    /// `None` when there is none (never added, or removed).
1562    pub fn field_id(&self, raw: u32) -> Option<FieldId> {
1563        self.fields
1564            .iter()
1565            .map(|f| f.id)
1566            .chain(self.thick.iter().map(|f| f.id))
1567            .chain(self.curv.iter().map(|f| f.id))
1568            .find(|id| id.0 == raw)
1569    }
1570
1571    fn field_index(&self, id: FieldId) -> Result<usize> {
1572        self.fields
1573            .iter()
1574            .position(|f| f.id == id)
1575            .ok_or(SceneError::UnknownField)
1576    }
1577
1578    /// Which registry `id` lives in.
1579    ///
1580    /// # Errors
1581    /// [`SceneError::UnknownField`] for an unknown or removed field.
1582    fn field_ref(&self, id: FieldId) -> Result<FieldRef> {
1583        if let Some(i) = self.fields.iter().position(|f| f.id == id) {
1584            return Ok(FieldRef::Distance(i));
1585        }
1586        if let Some(t) = self.thick.iter().position(|f| f.id == id) {
1587            return Ok(FieldRef::Thickness(t));
1588        }
1589        if let Some(c) = self.curv.iter().position(|f| f.id == id) {
1590            return Ok(FieldRef::Curvature(c));
1591        }
1592        Err(SceneError::UnknownField)
1593    }
1594
1595    /// The index of thickness field `id`.
1596    ///
1597    /// # Errors
1598    /// [`SceneError::UnknownField`] for an unknown or removed field, or a
1599    /// field of another kind.
1600    fn thick_index(&self, id: FieldId) -> Result<usize> {
1601        self.thick
1602            .iter()
1603            .position(|f| f.id == id)
1604            .ok_or(SceneError::UnknownField)
1605    }
1606
1607    /// The index of curvature field `id`.
1608    ///
1609    /// # Errors
1610    /// [`SceneError::UnknownField`] for an unknown or removed field, or a
1611    /// field of another kind.
1612    fn curv_index(&self, id: FieldId) -> Result<usize> {
1613        self.curv
1614            .iter()
1615            .position(|f| f.id == id)
1616            .ok_or(SceneError::UnknownField)
1617    }
1618
1619    /// Computed at least once, and nothing changed on its mesh or a target
1620    /// since.
1621    fn field_current(&self, i: usize) -> bool {
1622        let f = &self.fields[i];
1623        let clean = |m: MeshId| {
1624            self.entry(m)
1625                .ok()
1626                .and_then(|e| e.hook(f.id))
1627                .is_some_and(FieldHook::is_clean)
1628        };
1629        let settled = !f.live.stale || self.field_interaction.is_some();
1630        settled && !f.fresh && clean(f.desc.on) && f.desc.to.iter().all(|&m| clean(m))
1631    }
1632
1633    /// Computed at least once, and nothing changed on its mesh since.
1634    fn thick_current(&self, t: usize) -> bool {
1635        let f = &self.thick[t];
1636        let clean = self
1637            .entry(f.desc.on)
1638            .ok()
1639            .and_then(|e| e.hook(f.id))
1640            .is_some_and(FieldHook::is_clean);
1641        !f.fresh && clean
1642    }
1643
1644    /// Computed at least once, and nothing changed on its mesh since.
1645    fn curv_current(&self, c: usize) -> bool {
1646        let f = &self.curv[c];
1647        let clean = self
1648            .entry(f.desc.on)
1649            .ok()
1650            .and_then(|e| e.hook(f.id))
1651            .is_some_and(FieldHook::is_clean);
1652        !f.fresh && clean
1653    }
1654
1655    /// Updates the fields that are not current, whose mesh's mode passes
1656    /// `want`, and (with `touched`) that involve one of those meshes.
1657    /// Returns the first error; every field is attempted.
1658    pub(crate) fn run_fields(
1659        &mut self,
1660        want: impl Fn(UpdateMode) -> bool,
1661        touched: Option<&[MeshId]>,
1662    ) -> Result<()> {
1663        let due = self.due_fields(&want, touched)?;
1664        let r = self.update_due(&due);
1665        // Thickness and curvature fields update separately (they hold no
1666        // shared unit), but every due field is still attempted.
1667        let due_thick = self.due_thick_fields(&want, touched)?;
1668        let r2 = self.update_thick_due(&due_thick);
1669        let due_curv = self.due_curv_fields(&want, touched)?;
1670        let r3 = self.update_curv_due(&due_curv);
1671        r.and(r2).and(r3)
1672    }
1673
1674    /// The indices of the thickness fields that are not current, whose
1675    /// mesh's mode passes `want`, and (with `touched`) that are on one of
1676    /// those meshes, ascending.
1677    fn due_thick_fields(
1678        &self,
1679        want: impl Fn(UpdateMode) -> bool,
1680        touched: Option<&[MeshId]>,
1681    ) -> Result<Vec<usize>> {
1682        let mut due = Vec::new();
1683        for t in 0..self.thick.len() {
1684            let on = self.thick[t].desc.on;
1685            if touched.is_some_and(|touched| !touched.contains(&on)) {
1686                continue;
1687            }
1688            let Ok(ea) = self.entry(on) else { continue };
1689            if !want(ea.mode) || self.thick_current(t) {
1690                continue;
1691            }
1692            due.try_reserve(1).map_err(|_| oom())?;
1693            due.push(t);
1694        }
1695        Ok(due)
1696    }
1697
1698    /// The indices of the curvature fields that are not current, whose
1699    /// mesh's mode passes `want`, and (with `touched`) that are on one of
1700    /// those meshes, ascending.
1701    fn due_curv_fields(
1702        &self,
1703        want: impl Fn(UpdateMode) -> bool,
1704        touched: Option<&[MeshId]>,
1705    ) -> Result<Vec<usize>> {
1706        let mut due = Vec::new();
1707        for c in 0..self.curv.len() {
1708            let on = self.curv[c].desc.on;
1709            if touched.is_some_and(|touched| !touched.contains(&on)) {
1710                continue;
1711            }
1712            let Ok(ea) = self.entry(on) else { continue };
1713            if !want(ea.mode) || self.curv_current(c) {
1714                continue;
1715            }
1716            due.try_reserve(1).map_err(|_| oom())?;
1717            due.push(c);
1718        }
1719        Ok(due)
1720    }
1721
1722    /// Brings curvature field `c` up to date: its BVH under the query rules,
1723    /// then every vertex recomputed in full. All or nothing: on error
1724    /// nothing is written and the pending changes stay.
1725    fn update_curv_field(&mut self, c: usize) -> Result<()> {
1726        self.update_curv_due(&[c])
1727    }
1728
1729    /// Brings the curvature fields at indices `due` (ascending) up to date,
1730    /// one after the other. A field that fails is left as it was. Returns
1731    /// the first error; every field is attempted.
1732    fn update_curv_due(&mut self, due: &[usize]) -> Result<()> {
1733        let mut first: Result<()> = Ok(());
1734        for &c in due {
1735            if let Err(e) = self.compute_curv_field(c)
1736                && first.is_ok()
1737            {
1738                first = Err(e);
1739            }
1740        }
1741        first
1742    }
1743
1744    /// Prepares curvature field `c`, computes every vertex through the
1745    /// placement, and applies the values. Fails, writing nothing, when the
1746    /// BVH cannot be brought up to date, the kernel fails, or memory runs
1747    /// out.
1748    fn compute_curv_field(&mut self, c: usize) -> Result<()> {
1749        use fast_mesh_bvh::BuildOptions;
1750        use fast_mesh_store::Mesh;
1751
1752        let (on, channel) = {
1753            let f = &self.curv[c];
1754            (f.desc.on, f.channel.clone())
1755        };
1756        // The mesh's own BVH is what the field's staleness tracks; the kernel
1757        // reads the mesh itself.
1758        self.prepare(Some(&[on]))?;
1759        let ea = self.entry(on)?;
1760        let (placement, origin) = (ea.placement, ea.mesh.origin());
1761        let slots = ea.mesh.vertex_slots();
1762        // Where the kernel runs, and what turns its local curvatures into
1763        // world ones: a rigid placement changes nothing, a uniform scale `s`
1764        // divides (curvature is 1/length), and anything else runs on a baked
1765        // world-frame copy.
1766        let mut scale = 1.0f32;
1767        let mut baked: Option<(Mesh, Bvh)> = None;
1768        if placement.is_scaled() {
1769            if let Some(s) = placement.uniform_scale() {
1770                scale = s as f32;
1771            } else {
1772                baked = Some(baked_mesh(
1773                    &ea.mesh,
1774                    |p| {
1775                        placement.apply([
1776                            origin[0] + f64::from(p[0]),
1777                            origin[1] + f64::from(p[1]),
1778                            origin[2] + f64::from(p[2]),
1779                        ])
1780                    },
1781                    &BuildOptions::default(),
1782                )?);
1783            }
1784        }
1785        let mut values = Vec::new();
1786        values.try_reserve_exact(slots).map_err(|_| oom())?;
1787        values.resize(slots, f32::NAN);
1788        match &baked {
1789            Some((mesh, _)) => {
1790                curvatures(mesh, &mut values)?;
1791            }
1792            None => {
1793                let eb = self.entry(on)?;
1794                curvatures(&eb.mesh, &mut values)?;
1795                if scale != 1.0 {
1796                    let s = f64::from(scale);
1797                    for v in &mut values {
1798                        if v.is_finite() {
1799                            *v = (*v as f64 / s) as f32;
1800                        }
1801                    }
1802                }
1803            }
1804        }
1805        drop(baked);
1806        // Commit: compare with the channel, then write, recolour and report.
1807        let id = self.curv[c].id;
1808        let ea = self.entries.get_mut(on).ok_or(SceneError::UnknownMesh)?;
1809        let old = ea.mesh.channel(&channel).ok_or(SceneError::Corrupted)?;
1810        let changed: Vec<VertexId> = (0..slots as VertexId)
1811            .filter(|&v| {
1812                old.get(v as usize).map(|o| o.to_bits()) != Some(values[v as usize].to_bits())
1813            })
1814            .collect();
1815        ea.mesh.derived_channel_mut(&channel)?[..slots].copy_from_slice(&values);
1816        ea.reset_hook(id, false);
1817        if !changed.is_empty() {
1818            ea.report_field(id);
1819        }
1820        if let Some(cs) = &mut ea.coloring
1821            && cs.coloring.field == id
1822        {
1823            let vals = ea.mesh.channel(&cs.channel).unwrap_or(&[]);
1824            let dirty = cs.update(vals, &changed);
1825            ea.report_colors(Some(&dirty));
1826        }
1827        let f = &mut self.curv[c];
1828        f.fresh = false;
1829        f.computed = true;
1830        Ok(())
1831    }
1832
1833    /// Brings thickness field `t` up to date: its BVH under the query rules,
1834    /// then every vertex recomputed in full. All or nothing: on error
1835    /// nothing is written and the pending changes stay.
1836    fn update_thick_field(&mut self, t: usize) -> Result<()> {
1837        self.update_thick_due(&[t])
1838    }
1839
1840    /// Brings the thickness fields at indices `due` (ascending) up to date,
1841    /// one after the other. A field that fails is left as it was. Returns
1842    /// the first error; every field is attempted.
1843    fn update_thick_due(&mut self, due: &[usize]) -> Result<()> {
1844        let mut first: Result<()> = Ok(());
1845        for &t in due {
1846            if let Err(e) = self.compute_thick_field(t)
1847                && first.is_ok()
1848            {
1849                first = Err(e);
1850            }
1851        }
1852        first
1853    }
1854
1855    /// Prepares thickness field `t` (its BVH), computes every vertex through
1856    /// the placement, and applies the values. Fails, writing nothing, when
1857    /// the BVH cannot be brought up to date, the kernel fails, or memory
1858    /// runs out.
1859    fn compute_thick_field(&mut self, t: usize) -> Result<()> {
1860        use fast_mesh_bvh::BuildOptions;
1861        use fast_mesh_store::Mesh;
1862
1863        let (on, max_distance, channel) = {
1864            let f = &self.thick[t];
1865            (f.desc.on, f.desc.max_distance, f.channel.clone())
1866        };
1867        // The mesh's own BVH is what the kernel reads.
1868        self.prepare(Some(&[on]))?;
1869        let ea = self.entry(on)?;
1870        let (placement, origin) = (ea.placement, ea.mesh.origin());
1871        let slots = ea.mesh.vertex_slots();
1872        // Where the kernel runs, and what turns its local distances into
1873        // world ones: a rigid placement changes nothing, a uniform scale
1874        // multiplies, and anything else runs on a baked world-frame copy.
1875        let mut scale = 1.0f32;
1876        let mut baked: Option<(Mesh, Bvh)> = None;
1877        if placement.is_scaled() {
1878            if let Some(s) = placement.uniform_scale() {
1879                scale = s as f32;
1880            } else {
1881                baked = Some(baked_mesh(
1882                    &ea.mesh,
1883                    |p| {
1884                        placement.apply([
1885                            origin[0] + f64::from(p[0]),
1886                            origin[1] + f64::from(p[1]),
1887                            origin[2] + f64::from(p[2]),
1888                        ])
1889                    },
1890                    &BuildOptions::default(),
1891                )?);
1892            }
1893        }
1894        let mut values = Vec::new();
1895        values.try_reserve_exact(slots).map_err(|_| oom())?;
1896        values.resize(slots, f32::NAN);
1897        match &baked {
1898            Some((mesh, bvh)) => {
1899                thicknesses(mesh, bvh, max_distance as f32, &mut values)?;
1900            }
1901            None => {
1902                let eb = self.entry(on)?;
1903                let bound = (max_distance / f64::from(scale)) as f32;
1904                thicknesses(&eb.mesh, &eb.bvh, bound, &mut values)?;
1905                if scale != 1.0 {
1906                    let s = f64::from(scale);
1907                    for v in &mut values {
1908                        if v.is_finite() {
1909                            *v = (*v as f64 * s) as f32;
1910                        }
1911                    }
1912                }
1913            }
1914        }
1915        drop(baked);
1916        // Commit: compare with the channel, then write, recolour and report.
1917        let id = self.thick[t].id;
1918        let ea = self.entries.get_mut(on).ok_or(SceneError::UnknownMesh)?;
1919        let old = ea.mesh.channel(&channel).ok_or(SceneError::Corrupted)?;
1920        let changed: Vec<VertexId> = (0..slots as VertexId)
1921            .filter(|&v| {
1922                old.get(v as usize).map(|o| o.to_bits()) != Some(values[v as usize].to_bits())
1923            })
1924            .collect();
1925        ea.mesh.derived_channel_mut(&channel)?[..slots].copy_from_slice(&values);
1926        ea.reset_hook(id, false);
1927        if !changed.is_empty() {
1928            ea.report_field(id);
1929        }
1930        if let Some(cs) = &mut ea.coloring
1931            && cs.coloring.field == id
1932        {
1933            let vals = ea.mesh.channel(&cs.channel).unwrap_or(&[]);
1934            let dirty = cs.update(vals, &changed);
1935            ea.report_colors(Some(&dirty));
1936        }
1937        let f = &mut self.thick[t];
1938        f.fresh = false;
1939        f.computed = true;
1940        Ok(())
1941    }
1942
1943    /// The indices of the fields that are not current, whose mesh's mode
1944    /// passes `want`, and (with `touched`) that involve one of those
1945    /// meshes, ascending.
1946    fn due_fields(
1947        &self,
1948        want: impl Fn(UpdateMode) -> bool,
1949        touched: Option<&[MeshId]>,
1950    ) -> Result<Vec<usize>> {
1951        let mut due = Vec::new();
1952        for i in 0..self.fields.len() {
1953            let (on, to) = (self.fields[i].desc.on, &self.fields[i].desc.to);
1954            if touched.is_some_and(|t| !t.contains(&on) && !to.iter().any(|m| t.contains(m))) {
1955                continue;
1956            }
1957            let Ok(ea) = self.entry(on) else { continue };
1958            if !want(ea.mode) || self.field_current(i) {
1959                continue;
1960            }
1961            due.try_reserve(1).map_err(|_| oom())?;
1962            due.push(i);
1963        }
1964        Ok(due)
1965    }
1966
1967    /// Updates the Eager fields involving `touched` (all with `None`).
1968    /// Failures are swallowed: the field stays stale (§11.2). With
1969    /// [`Scene::set_defer_fields`] on, does nothing: the fields stay due.
1970    pub(crate) fn run_eager_fields(&mut self, touched: Option<&[MeshId]>) {
1971        if self.defer_fields {
1972            return;
1973        }
1974        let _ = self.run_fields(|m| m == UpdateMode::Eager, touched);
1975    }
1976
1977    /// Defers Eager field updates: with `on`, the edits, placement changes,
1978    /// undos, redos, cancels and [`Scene::update`] calls that would update
1979    /// the Eager fields involving a mesh leave them due instead, for
1980    /// [`Scene::field_step`] (or [`Scene::update_fields`] or
1981    /// [`Scene::flush`]) to bring up to date. Registering a field on an
1982    /// Eager mesh still computes it at once. Leaves an open step unit open.
1983    ///
1984    /// Turning it off does not bring fields that are already due up to
1985    /// date: they wait for the next change involving their meshes, or for
1986    /// [`Scene::field_step`], [`Scene::update_fields`] or [`Scene::flush`].
1987    pub fn set_defer_fields(&mut self, on: bool) {
1988        self.defer_fields = on;
1989    }
1990
1991    /// Starts (`Some(points)`) or ends (`None`) an interaction: a gesture
1992    /// that changes a placement or a mesh many times a second, during which
1993    /// a distance field too large to recompute per update stays live near
1994    /// the contact and is made exact when the gesture ends.
1995    ///
1996    /// During an interaction, a field with more finite values than `points`
1997    /// updates at most `points` vertices closest to the targets (no pair
1998    /// search: a distance changes no faster than the motion, so its last value
1999    /// and how far the meshes have moved since say which can be close), the
2000    /// vertices around an edit, and a sweep of an eighth of `points` further
2001    /// vertices. The others keep their values, so that nothing turns far or
2002    /// loses its colour; those values are out of date by at most how far the
2003    /// meshes moved since they were computed (for an edit of a target, by how
2004    /// far it moved its surface, away from the edit's box), and
2005    /// [`Scene::field_status`] does not report it. The band of the closest
2006    /// vertices follows the work from the counts alone, so results are the
2007    /// same with `parallel` on or off, for any thread count. A field of at
2008    /// most `points` finite values updates exactly as usual.
2009    ///
2010    /// Ending the interaction recomputes every vertex of the fields it left
2011    /// out of date, at once for Eager meshes unless fields are deferred (see
2012    /// [`Scene::set_defer_fields`] and [`Scene::field_step`]); the values,
2013    /// colours and reports are then those of an exact update. Starting one
2014    /// again before that keeps them on the tier, and a gesture that changed
2015    /// nothing leaves nothing to recompute. Drops an open step unit when
2016    /// the setting changes.
2017    ///
2018    /// # Errors
2019    /// [`SceneError::InvalidInput`] when `points` is `Some(0)`. The scene
2020    /// is unchanged on error.
2021    pub fn set_field_interaction(&mut self, points: Option<usize>) -> Result<()> {
2022        if points == Some(0) {
2023            return Err(SceneError::InvalidInput(
2024                "an interaction's budget must be at least one point",
2025            ));
2026        }
2027        if self.field_interaction == points {
2028            return Ok(());
2029        }
2030        self.drop_field_unit();
2031        self.field_interaction = points;
2032        if points.is_none() {
2033            self.run_eager_fields(None);
2034        }
2035        Ok(())
2036    }
2037
2038    /// The budget of the interaction under way (see
2039    /// [`Scene::set_field_interaction`]), if any.
2040    pub fn field_interaction(&self) -> Option<usize> {
2041        self.field_interaction
2042    }
2043
2044    /// Computes part of the Eager fields that are due, so that a long
2045    /// update can be spread over several calls (a Worker yields between
2046    /// them).
2047    ///
2048    /// The first call opens a unit: it takes every due field on an Eager
2049    /// mesh (as an Eager update would) and brings the BVHs each reads up to
2050    /// date. Every call, the first included, then advances the unit's
2051    /// fields in field order, writing nothing: for each, the build of its
2052    /// targets' sign data when they are not current (about `budget` times
2053    /// 32 elements of the build per call), its near search (whole parts,
2054    /// about `budget` times 16 triangle pairs per call), the choice of the
2055    /// vertices it recomputes (see the module docs), and the values of up
2056    /// to `budget` of those vertices per call. A call that builds sign data
2057    /// or searches does only that. The call that finishes the last field
2058    /// applies every field of the unit together and returns
2059    /// [`StepState::Done`]: values, colours and reports are then the same,
2060    /// bit for bit, as from [`Scene::update_fields`] at the state the unit
2061    /// opened at.
2062    ///
2063    /// An open unit, including one opened by [`Scene::field_step_field`],
2064    /// is continued. Any other `&mut self` call on the scene drops it first
2065    /// (see [`Scene::abandon_field_step`]), except
2066    /// [`Scene::abandon_field_step`] and [`Scene::set_defer_fields`].
2067    ///
2068    /// A unit leaves a trace when it is then abandoned: the BVHs it brought
2069    /// up to date stay refitted, and the sign data it finished building
2070    /// stay with their fields, as after any query that needs them. A sign
2071    /// data build it left unfinished is kept with its field, and a later
2072    /// unit resumes it while its mesh and BVH are unchanged. No value,
2073    /// colour, report or pending change depends on whether that happened.
2074    ///
2075    /// # Errors
2076    /// Errors are isolated per field, as in [`Scene::update_fields`]: a
2077    /// field that fails, when the unit opens (for example
2078    /// [`SceneError::StaleBvh`] for a stale Manual mesh) or while it is
2079    /// advanced, is dropped from the unit and stays stale and due, as after
2080    /// a failed Eager update. The other fields go on, and the call that
2081    /// applies them returns the first error in field order instead of
2082    /// [`StepState::Done`]. When no due field can be opened, the first
2083    /// error is returned and no unit is opened.
2084    pub fn field_step(&mut self, budget: FieldBudget) -> Result<StepState> {
2085        self.step_unit(None, budget)
2086    }
2087
2088    /// [`Scene::field_step`] for field `id` alone, whatever its mesh's
2089    /// mode: opens a unit over it when it is not current and no unit for it
2090    /// is open (dropping another open unit), or continues its open unit.
2091    /// Returns [`StepState::Idle`] when the field is current. The call that
2092    /// finishes it applies it, with the same values, colours and reports as
2093    /// [`Scene::update_fields`] would give it. A thickness or curvature field
2094    /// holds no incremental state: stepping it computes it in full at once.
2095    ///
2096    /// # Errors
2097    /// [`SceneError::UnknownField`] for an unknown or removed field; then
2098    /// as for [`Scene::field_step`].
2099    pub fn field_step_field(&mut self, id: FieldId, budget: FieldBudget) -> Result<StepState> {
2100        self.field_ref(id)?;
2101        self.step_unit(Some(id), budget)
2102    }
2103
2104    /// Steps the open unit, or opens one over the due Eager fields
2105    /// (`only`: `None`) or over the field `only` (see [`Scene::field_step`]
2106    /// and [`Scene::field_step_field`]). A thickness or curvature field
2107    /// stepped alone computes in full at once; with `None`, the due Eager
2108    /// thickness and curvature fields compute in full alongside the distance
2109    /// unit (their values, colours and reports are the same as from
2110    /// [`Scene::update_fields`], applied when the unit applies).
2111    fn step_unit(&mut self, only: Option<FieldId>, budget: FieldBudget) -> Result<StepState> {
2112        let FieldBudget::Points(points) = budget;
2113        // A thickness or curvature field holds no incremental state: compute
2114        // it in full.
2115        if let Some(id) = only
2116            && let Ok(t) = self.thick_index(id)
2117        {
2118            self.drop_field_unit();
2119            if self.thick_current(t) {
2120                return Ok(StepState::Idle);
2121            }
2122            self.update_thick_field(t)?;
2123            return Ok(StepState::Done);
2124        }
2125        if let Some(id) = only
2126            && let Ok(c) = self.curv_index(id)
2127        {
2128            self.drop_field_unit();
2129            if self.curv_current(c) {
2130                return Ok(StepState::Idle);
2131            }
2132            self.update_curv_field(c)?;
2133            return Ok(StepState::Done);
2134        }
2135        // With no single field, due Eager thickness and curvature fields
2136        // compute in full first, then the distance unit advances as usual.
2137        let mut thick_worked = false;
2138        let mut thick_error: Result<()> = Ok(());
2139        if only.is_none() {
2140            match self.due_thick_fields(|m| m == UpdateMode::Eager, None) {
2141                Ok(due) => {
2142                    thick_worked = !due.is_empty();
2143                    thick_error = self.update_thick_due(&due);
2144                }
2145                Err(e) => thick_error = Err(e),
2146            }
2147            match self.due_curv_fields(|m| m == UpdateMode::Eager, None) {
2148                Ok(due) => {
2149                    thick_worked = thick_worked || !due.is_empty();
2150                    thick_error = thick_error.and(self.update_curv_due(&due));
2151                }
2152                Err(e) => thick_error = thick_error.and(Err(e)),
2153            }
2154        }
2155        let open = self
2156            .field_unit
2157            .take()
2158            .filter(|u| only.is_none() || u.only == only);
2159        let mut unit = match open {
2160            Some(unit) => unit,
2161            None => match self.open_field_unit(only)? {
2162                Some(unit) => unit,
2163                None => {
2164                    // No distance unit: the step did what the thickness and
2165                    // curvature fields needed, if anything.
2166                    thick_error?;
2167                    return Ok(if thick_worked {
2168                        StepState::Done
2169                    } else {
2170                        StepState::Idle
2171                    });
2172                }
2173            },
2174        };
2175        if !unit.finished {
2176            self.advance_field_unit(&mut unit, points.max(1));
2177            if unit.next < unit.fields.len() {
2178                self.field_unit = Some(unit);
2179                return Ok(StepState::Pending);
2180            }
2181            // Applying writes every candidate's value and colour: when there
2182            // are more than a step's worth, in a step of its own.
2183            let written: usize = unit
2184                .fields
2185                .iter()
2186                .filter_map(|f| f.plan.as_ref())
2187                .map(|p| p.candidates.len())
2188                .sum();
2189            if points != usize::MAX && written > points {
2190                unit.finished = true;
2191                self.field_unit = Some(unit);
2192                return Ok(StepState::Pending);
2193            }
2194        }
2195        for f in unit.fields {
2196            // A field that failed has no plan.
2197            if let Some(plan) = f.plan {
2198                let i = f.job.i;
2199                // Fails only when the field's channel or mesh is missing.
2200                if let Err(e) = self.apply_field(f.job, plan) {
2201                    unit.error = unit.error.filter(|(j, _)| *j < i).or(Some((i, e)));
2202                }
2203            }
2204        }
2205        // Thickness and curvature values are already applied; their error
2206        // joins the unit's.
2207        if thick_error.is_err() && unit.error.is_none() {
2208            return thick_error.map(|()| StepState::Done);
2209        }
2210        match unit.error {
2211            Some((_, e)) => Err(e),
2212            None => Ok(StepState::Done),
2213        }
2214    }
2215
2216    /// Drops the open step unit, if any: nothing it computed is written or
2217    /// reported, and its fields stay due. Does nothing when no unit is
2218    /// open. What the unit brought up to date (BVHs, finished sign data)
2219    /// stays so, and an unfinished sign data build is kept; see
2220    /// [`Scene::field_step`].
2221    pub fn abandon_field_step(&mut self) {
2222        self.drop_field_unit();
2223    }
2224
2225    /// Whether a step unit is open (see [`Scene::field_step`]).
2226    pub fn field_step_pending(&self) -> bool {
2227        self.field_unit.is_some()
2228    }
2229
2230    /// The meshes the fields of the open step unit are on and measured to,
2231    /// each once, in field order; empty when no unit is open. A change to
2232    /// any of them makes the unit stale.
2233    pub fn field_step_meshes(&self) -> Vec<MeshId> {
2234        let mut meshes = Vec::new();
2235        for f in self.field_unit.iter().flat_map(|u| &u.fields) {
2236            let desc = &self.fields[f.job.i].desc;
2237            for &m in std::iter::once(&desc.on).chain(&desc.to) {
2238                if !meshes.contains(&m) {
2239                    meshes.push(m);
2240                }
2241            }
2242        }
2243        meshes
2244    }
2245
2246    /// Drops the open step unit, if any. Every public `&mut self` method of
2247    /// the scene but the step calls starts with this, as a unit holds field
2248    /// indices and values computed from the scene as it was. A new public
2249    /// `&mut self` method must call it first too, and join the list in the
2250    /// test `mutating_calls_abandon_open_unit`.
2251    pub(crate) fn drop_field_unit(&mut self) {
2252        self.field_unit = None;
2253        self.drop_stale_sign_builds();
2254    }
2255
2256    /// Drops the unfinished sign data builds kept with the fields that are
2257    /// no longer for their mesh and BVH as they are (an edit since, say):
2258    /// they would only start over, and hold arrays the size of the mesh.
2259    fn drop_stale_sign_builds(&mut self) {
2260        let entries = &self.entries;
2261        for f in &mut self.fields {
2262            for (k, slot) in f.sign_builds.iter_mut().enumerate() {
2263                let stale = slot.as_ref().is_some_and(|b| {
2264                    entries
2265                        .get(f.desc.to[k])
2266                        .is_none_or(|e| !b.is_for(&e.mesh, &e.bvh))
2267                });
2268                if stale {
2269                    *slot = None;
2270                }
2271            }
2272        }
2273    }
2274
2275    /// The candidates of the open step unit left to compute, counting only
2276    /// fields whose candidates are chosen.
2277    #[cfg(test)]
2278    pub(crate) fn field_step_left(&self) -> usize {
2279        self.field_unit.as_ref().map_or(0, |u| {
2280            u.fields[u.next..]
2281                .iter()
2282                .map(|f| f.pick.candidates.len() - f.values.len())
2283                .sum()
2284        })
2285    }
2286
2287    /// Unfinished sign data builds kept with the fields.
2288    #[cfg(test)]
2289    fn kept_sign_builds(&self) -> usize {
2290        self.fields
2291            .iter()
2292            .flat_map(|f| &f.sign_builds)
2293            .filter(|b| b.is_some())
2294            .count()
2295    }
2296
2297    /// The stage of the open step unit's first unfinished field.
2298    #[cfg(test)]
2299    fn field_step_stage(&self) -> Option<Stage> {
2300        let u = self.field_unit.as_ref()?;
2301        u.fields.get(u.next).map(|f| f.stage)
2302    }
2303
2304    /// Opens a step unit over the due Eager fields, or over the field
2305    /// `only` when it is not current: prepares each (BVHs, what it
2306    /// recomputes), leaving out (with their errors) those that fail. `None`
2307    /// when no field is due; the first error when none opens.
2308    fn open_field_unit(&mut self, only: Option<FieldId>) -> Result<Option<FieldUnit>> {
2309        let due = match only {
2310            None => self.due_fields(|m| m == UpdateMode::Eager, None)?,
2311            Some(id) => {
2312                let i = self.field_index(id)?;
2313                if self.field_current(i) {
2314                    Vec::new()
2315                } else {
2316                    vec![i]
2317                }
2318            }
2319        };
2320        if due.is_empty() {
2321            return Ok(None);
2322        }
2323        let mut unit = FieldUnit {
2324            only,
2325            fields: Vec::new(),
2326            next: 0,
2327            finished: false,
2328            error: None,
2329        };
2330        unit.fields
2331            .try_reserve_exact(due.len())
2332            .map_err(|_| oom())?;
2333        for &i in &due {
2334            match self.prepare_job(i) {
2335                Ok(job) => {
2336                    let all = job.full;
2337                    unit.fields.push(UnitField {
2338                        job,
2339                        stage: Stage::Sign(0),
2340                        search: None,
2341                        near: Vec::new(),
2342                        all,
2343                        aborted: false,
2344                        pick: Pick::default(),
2345                        values: Vec::new(),
2346                        nearest: Vec::new(),
2347                        recomputed: 0,
2348                        plan: None,
2349                    });
2350                }
2351                Err(e) => unit.fail(i, e),
2352            }
2353        }
2354        if unit.fields.is_empty() {
2355            return Err(unit.error.map_or(SceneError::Corrupted, |(_, e)| e));
2356        }
2357        Ok(Some(unit))
2358    }
2359
2360    /// Advances `unit`'s fields in field order with a budget of `points`
2361    /// (see [`Scene::field_step`]), giving each field whose values are all
2362    /// computed its plan. A field that fails is dropped from the unit, its
2363    /// error recorded: it gets no plan, and its work left costs nothing.
2364    fn advance_field_unit(&mut self, unit: &mut FieldUnit, points: usize) {
2365        let mut step = Step {
2366            points,
2367            left: points,
2368            worked: false,
2369        };
2370        while let Some(f) = unit.fields.get_mut(unit.next) {
2371            let i = f.job.i;
2372            match self.advance_field(f, &mut step) {
2373                Ok(false) => return,
2374                Ok(true) => {}
2375                Err(e) => {
2376                    f.search = None;
2377                    unit.fail(i, e);
2378                }
2379            }
2380            unit.next += 1;
2381        }
2382    }
2383
2384    /// Advances one field of a unit through its stages within `step`.
2385    /// Returns `true` once the field has its plan, `false` when the step is
2386    /// spent first.
2387    fn advance_field(&mut self, f: &mut UnitField, step: &mut Step) -> Result<bool> {
2388        let i = f.job.i;
2389        let targets = self.fields[i].desc.to.len();
2390        loop {
2391            match f.stage {
2392                Stage::Sign(k) if k == targets => f.stage = Stage::Near(0),
2393                Stage::Sign(k) => {
2394                    if self.sign_current(i, k)? {
2395                        f.stage = Stage::Sign(k + 1);
2396                        continue;
2397                    }
2398                    if !step.begin_heavy() {
2399                        return Ok(false);
2400                    }
2401                    let elems = step.points.saturating_mul(SIGN_ELEMS_PER_POINT);
2402                    if self.step_sign(i, k, elems)? {
2403                        f.stage = Stage::Sign(k + 1);
2404                    }
2405                    if !step.unbounded() {
2406                        return Ok(false);
2407                    }
2408                }
2409                Stage::Near(k) if k == targets || f.all => f.stage = Stage::Pick,
2410                Stage::Near(k) => {
2411                    let Some(margin) = f.job.near_margins[k] else {
2412                        f.stage = Stage::Near(k + 1);
2413                        continue;
2414                    };
2415                    if !step.begin_heavy() {
2416                        return Ok(false);
2417                    }
2418                    let (on, to) = (self.fields[i].desc.on, self.fields[i].desc.to[k]);
2419                    let (ea, eb) = (self.entry(on)?, self.entry(to)?);
2420                    let search = match &mut f.search {
2421                        Some(search) => search,
2422                        empty => empty.insert(NearSearch::new(
2423                            &ea.mesh,
2424                            &ea.bvh,
2425                            &eb.mesh,
2426                            &eb.bvh,
2427                            &f.job.b_to_a[k],
2428                            round_up(margin),
2429                            f.job.near_limit,
2430                        )?),
2431                    };
2432                    let tests = step.points.saturating_mul(NEAR_TESTS_PER_POINT);
2433                    if search.step(&ea.mesh, &ea.bvh, &eb.mesh, &eb.bvh, tests)? {
2434                        let done = f.search.take().ok_or(SceneError::Corrupted)?;
2435                        let within = done.finish(&mut f.near)?.ok_or(SceneError::Corrupted)?;
2436                        f.aborted = !within;
2437                        f.all = f.aborted;
2438                        f.stage = Stage::Near(k + 1);
2439                    }
2440                    if !step.unbounded() {
2441                        return Ok(false);
2442                    }
2443                }
2444                Stage::Pick => {
2445                    // Choosing every vertex, or scanning for flipped ones,
2446                    // walks every vertex slot: a step of its own.
2447                    if (f.all || f.job.topology || f.job.live) && !step.begin_heavy() {
2448                        return Ok(false);
2449                    }
2450                    let near = std::mem::take(&mut f.near);
2451                    f.pick = self.assemble_pick(&f.job, &near, f.all, f.aborted)?;
2452                    let n = f.pick.candidates.len();
2453                    f.values.try_reserve_exact(n).map_err(|_| oom())?;
2454                    f.nearest.try_reserve_exact(n).map_err(|_| oom())?;
2455                    f.stage = Stage::Values;
2456                }
2457                Stage::Values => {
2458                    let (done, total) = (f.values.len(), f.pick.candidates.len());
2459                    if done < total {
2460                        if (step.worked && !step.unbounded()) || step.left == 0 {
2461                            return Ok(false);
2462                        }
2463                        let end = done + step.left.min(total - done);
2464                        let run = &f.pick.candidates[done..end];
2465                        f.recomputed +=
2466                            self.compute_values(&f.job, run, &mut f.values, &mut f.nearest)?;
2467                        step.left -= end - done;
2468                        if end < total {
2469                            return Ok(false);
2470                        }
2471                    }
2472                    f.stage = Stage::Finish;
2473                }
2474                Stage::Finish => {
2475                    // A full plan walks every vertex slot: a step of its own.
2476                    if f.all && !step.begin_heavy() {
2477                        return Ok(false);
2478                    }
2479                    let pick = std::mem::take(&mut f.pick);
2480                    let values = std::mem::take(&mut f.values);
2481                    let nearest = std::mem::take(&mut f.nearest);
2482                    f.plan =
2483                        Some(self.finish_field(&f.job, pick, values, nearest, f.recomputed)?);
2484                    return Ok(true);
2485                }
2486            }
2487        }
2488    }
2489
2490    /// Whether field `i`'s sign data of target `k` are current. A baked
2491    /// target (see [`Scene::target_uses_temp`]) needs none: its real sign
2492    /// data are unused.
2493    fn sign_current(&self, i: usize, k: usize) -> Result<bool> {
2494        if self.target_uses_temp(i, k) {
2495            return Ok(true);
2496        }
2497        let f = &self.fields[i];
2498        let eb = self.entry(f.desc.to[k])?;
2499        Ok(f.sign[k]
2500            .as_ref()
2501            .is_some_and(|s| s.is_current(&eb.mesh, &eb.bvh)))
2502    }
2503
2504    /// Runs the build of field `i`'s sign data of target `k` over up to
2505    /// `elems` more elements, resuming the field's unfinished build when it
2506    /// is for the mesh and BVH as they are, starting one otherwise. Stores
2507    /// the sign data when done, and returns whether it is.
2508    fn step_sign(&mut self, i: usize, k: usize, elems: usize) -> Result<bool> {
2509        let f = &mut self.fields[i];
2510        let eb = self
2511            .entries
2512            .get_mut(f.desc.to[k])
2513            .ok_or(SceneError::UnknownMesh)?;
2514        let slot = &mut f.sign_builds[k];
2515        let resuming = slot.as_ref().is_some_and(|b| b.is_for(&eb.mesh, &eb.bvh));
2516        // A small position-only edit refits around the dirty vertices, in
2517        // this one step, with the bits of a build (see `SignData::try_refit`).
2518        // Past about what a step of a build takes, it builds in steps.
2519        if !resuming
2520            && let (Some(sign), Some(changes)) = (
2521                &mut f.sign[k],
2522                eb.hook(f.id).and_then(FieldHook::refit_changes),
2523            )
2524            && changes.vertices_moved.len() <= elems / SIGN_ELEMS_PER_REFIT_VERTEX
2525            && sign.try_refit(&eb.mesh, &eb.bvh, changes)?
2526        {
2527            *slot = None;
2528            return Ok(true);
2529        }
2530        let build = match slot {
2531            Some(b) if b.is_for(&eb.mesh, &eb.bvh) => b,
2532            _ => slot.insert(SignBuild::new(&eb.mesh, &eb.bvh, f.desc.sign)?),
2533        };
2534        #[cfg(test)]
2535        counter::add_sign_step();
2536        match build.step(&mut eb.mesh, &eb.bvh, elems) {
2537            Ok(false) => Ok(false),
2538            Ok(true) => {
2539                f.sign[k] = slot.take().and_then(SignBuild::finish);
2540                Ok(true)
2541            }
2542            Err(e) => {
2543                *slot = None;
2544                Err(e.into())
2545            }
2546        }
2547    }
2548
2549    /// Removes the fields on `mesh` or measured to it alone, and removes
2550    /// `mesh` from the targets of the fields that have others; those are
2551    /// recomputed in full on their next update. Returns the meshes those
2552    /// fields are on, each once.
2553    pub(crate) fn drop_fields_of(&mut self, mesh: MeshId) -> Vec<MeshId> {
2554        let mut kept_on = Vec::new();
2555        let mut i = 0;
2556        while i < self.fields.len() {
2557            let f = &mut self.fields[i];
2558            let target = f.desc.to.iter().position(|&m| m == mesh);
2559            if f.desc.on == mesh || (target.is_some() && f.desc.to.len() == 1) {
2560                self.detach_field(i);
2561                continue;
2562            }
2563            if let Some(k) = target {
2564                f.desc.to.remove(k);
2565                f.sign.remove(k);
2566                f.sign_builds.remove(k);
2567                f.fresh = true;
2568                // Until then, the indices follow the remaining targets.
2569                let k = k as u32;
2570                for n in &mut f.nearest {
2571                    if *n == k {
2572                        *n = u32::MAX;
2573                    } else if *n != u32::MAX && *n > k {
2574                        *n -= 1;
2575                    }
2576                }
2577                let (id, on) = (f.id, f.desc.on);
2578                if let Some(e) = self.entries.get_mut(mesh) {
2579                    e.hooks.retain(|h| h.field != id);
2580                }
2581                if !kept_on.contains(&on) {
2582                    kept_on.push(on);
2583                }
2584            }
2585            i += 1;
2586        }
2587        // A thickness or curvature field lives on its mesh alone: removing
2588        // the mesh removes the field.
2589        let mut t = 0;
2590        while t < self.thick.len() {
2591            if self.thick[t].desc.on == mesh {
2592                self.detach_thick(t);
2593            } else {
2594                t += 1;
2595            }
2596        }
2597        let mut c = 0;
2598        while c < self.curv.len() {
2599            if self.curv[c].desc.on == mesh {
2600                self.detach_curv(c);
2601            } else {
2602                c += 1;
2603            }
2604        }
2605        kept_on
2606    }
2607
2608    fn detach_field(&mut self, i: usize) {
2609        let f = self.fields.remove(i);
2610        for &to in &f.desc.to {
2611            if let Some(eb) = self.entries.get_mut(to) {
2612                eb.hooks.retain(|h| h.field != f.id);
2613            }
2614        }
2615        if let Some(ea) = self.entries.get_mut(f.desc.on) {
2616            ea.hooks.retain(|h| h.field != f.id);
2617            let _ = ea.mesh.remove_channel(&f.channel);
2618            if ea
2619                .coloring
2620                .as_ref()
2621                .is_some_and(|c| c.coloring.field == f.id)
2622            {
2623                ea.coloring = None;
2624                ea.report_colors(None);
2625            }
2626            ea.report_field(f.id);
2627        }
2628    }
2629
2630    /// Removes thickness field `t`: as [`Scene::detach_field`] without
2631    /// targets.
2632    fn detach_thick(&mut self, t: usize) {
2633        let f = self.thick.remove(t);
2634        if let Some(ea) = self.entries.get_mut(f.desc.on) {
2635            ea.hooks.retain(|h| h.field != f.id);
2636            let _ = ea.mesh.remove_channel(&f.channel);
2637            if ea
2638                .coloring
2639                .as_ref()
2640                .is_some_and(|c| c.coloring.field == f.id)
2641            {
2642                ea.coloring = None;
2643                ea.report_colors(None);
2644            }
2645            ea.report_field(f.id);
2646        }
2647    }
2648
2649    /// Removes curvature field `c`: as [`Scene::detach_field`] without
2650    /// targets.
2651    fn detach_curv(&mut self, c: usize) {
2652        let f = self.curv.remove(c);
2653        if let Some(ea) = self.entries.get_mut(f.desc.on) {
2654            ea.hooks.retain(|h| h.field != f.id);
2655            let _ = ea.mesh.remove_channel(&f.channel);
2656            if ea
2657                .coloring
2658                .as_ref()
2659                .is_some_and(|c| c.coloring.field == f.id)
2660            {
2661                ea.coloring = None;
2662                ea.report_colors(None);
2663            }
2664            ea.report_field(f.id);
2665        }
2666    }
2667
2668    /// The largest absolute coordinate of A's root box, of B's root box moved
2669    /// into A's space (the frame of the pair test), as in Phase 3b's pair
2670    /// pruning, and of B's root box in B's own space (the kernel rounds in
2671    /// B's local `f32` frame): the magnitude the rounding of either side
2672    /// scales with. 0 for a mesh without triangles.
2673    fn pair_magnitude(&self, on: MeshId, to: MeshId) -> Result<f64> {
2674        let xf = self.relative_transform(on, to)?;
2675        let root = |m: MeshId| -> Result<Option<_>> {
2676            Ok(self.entry(m)?.bvh.root_bounds().filter(|b| !b.is_empty()))
2677        };
2678        let mut m = root(on)?.map_or(0.0, |b| box_magnitude(b.min, b.max));
2679        if let Some(b) = root(to)? {
2680            m = m.max(box_magnitude(b.min, b.max));
2681            for k in 0..8 {
2682                let corner =
2683                    std::array::from_fn(|d| if k >> d & 1 == 0 { b.min[d] } else { b.max[d] });
2684                let p = xf.apply(corner);
2685                m = p.iter().fold(m, |m, c| m.max(c.abs()));
2686            }
2687        }
2688        Ok(m)
2689    }
2690
2691    /// Whether B's root box, moved into A's space by `b_to_a`, comes within
2692    /// `margin` of A's root box: when it does not, the near search ends at
2693    /// the root pair. False when either mesh has no triangles. Only for the
2694    /// work estimate, so the moved box is the bounds of its moved corners.
2695    fn roots_within(&self, on: MeshId, to: MeshId, b_to_a: &Rigid, margin: f64) -> Result<bool> {
2696        let root = |m: MeshId| -> Result<Option<_>> {
2697            Ok(self.entry(m)?.bvh.root_bounds().filter(|b| !b.is_empty()))
2698        };
2699        let (Some(a), Some(b)) = (root(on)?, root(to)?) else {
2700            return Ok(false);
2701        };
2702        let (mut lo, mut hi) = ([f64::INFINITY; 3], [f64::NEG_INFINITY; 3]);
2703        for k in 0..8 {
2704            let corner = std::array::from_fn(|d| if k >> d & 1 == 0 { b.min[d] } else { b.max[d] });
2705            let p = b_to_a.apply(corner);
2706            for d in 0..3 {
2707                lo[d] = lo[d].min(p[d]);
2708                hi[d] = hi[d].max(p[d]);
2709            }
2710        }
2711        let gap2: f64 = (0..3)
2712            .map(|d| {
2713                let g = (f64::from(a.min[d]) - hi[d])
2714                    .max(lo[d] - f64::from(a.max[d]))
2715                    .max(0.0);
2716                g * g
2717            })
2718            .sum();
2719        Ok(gap2.sqrt() <= margin)
2720    }
2721
2722    /// Brings field `i` up to date (see the module docs for what is
2723    /// recomputed). All or nothing: on error nothing is written and the
2724    /// pending changes stay.
2725    fn update_field(&mut self, i: usize) -> Result<()> {
2726        self.update_due(&[i])
2727    }
2728
2729    /// Brings the fields at indices `due` (ascending) up to date, in three
2730    /// phases: each is prepared in turn ([`Scene::prepare_field`], which
2731    /// needs the meshes mutably), then all are computed without writing
2732    /// (in parallel across fields when the work is large enough), then each
2733    /// is applied in field order, so that values, colours and reports come
2734    /// out as if the fields were updated one after the other. A field that
2735    /// fails at any phase is left as it was. Returns the first error in field
2736    /// order; every field is attempted.
2737    ///
2738    /// Preparing a field before the earlier ones are applied changes
2739    /// nothing: preparing writes no field state and reports nothing, and a
2740    /// field's update reads only its own channel, sign data and hooks.
2741    fn update_due(&mut self, due: &[usize]) -> Result<()> {
2742        let mut outcomes: Vec<Result<()>> = Vec::new();
2743        outcomes.try_reserve_exact(due.len()).map_err(|_| oom())?;
2744        let mut jobs: Vec<(usize, FieldJob)> = Vec::new();
2745        jobs.try_reserve_exact(due.len()).map_err(|_| oom())?;
2746        for (k, &i) in due.iter().enumerate() {
2747            match self.prepare_field(i) {
2748                Ok(job) => {
2749                    jobs.push((k, job));
2750                    outcomes.push(Ok(()));
2751                }
2752                Err(e) => outcomes.push(Err(e)),
2753            }
2754        }
2755        self.share_pair_queries(&mut jobs)?;
2756        match self.compute_fields(&jobs) {
2757            Ok(plans) => {
2758                for ((k, job), plan) in jobs.into_iter().zip(plans) {
2759                    outcomes[k] = plan.and_then(|plan| self.apply_field(job, plan));
2760                }
2761            }
2762            Err(e) => {
2763                for (k, _) in jobs {
2764                    outcomes[k] = Err(e.clone());
2765                }
2766            }
2767        }
2768        outcomes.into_iter().find(Result::is_err).unwrap_or(Ok(()))
2769    }
2770
2771    /// Runs one pair query for each reciprocal pair of single-target fields
2772    /// among `jobs` that both need a near set and share the same
2773    /// `max_distance`, storing each side's near triangles in its job so that
2774    /// [`Scene::pick_candidates`] runs no second query. With colouring on
2775    /// both sides, A→B and B→A would otherwise each run `triangles_near`
2776    /// and throw away the other half; one query roughly halves the
2777    /// two-sided drag cost.
2778    fn share_pair_queries(&self, jobs: &mut [(usize, FieldJob)]) -> Result<()> {
2779        use std::collections::HashMap;
2780        let mut groups: HashMap<(MeshId, MeshId), Vec<usize>> = HashMap::new();
2781        for (j, (_, job)) in jobs.iter().enumerate() {
2782            if job.full || job.shared_near.is_some() {
2783                continue;
2784            }
2785            let f = &self.fields[job.i];
2786            if f.desc.to.len() != 1 || job.near_margins.len() != 1 {
2787                continue;
2788            }
2789            if job.near_margins[0].is_none() {
2790                continue;
2791            }
2792            let (a, b) = (f.desc.on, f.desc.to[0]);
2793            let key = if a < b { (a, b) } else { (b, a) };
2794            groups.entry(key).or_default().push(j);
2795        }
2796        for js in groups.into_values() {
2797            let mut used = vec![false; js.len()];
2798            for (x, &jx) in js.iter().enumerate() {
2799                if used[x] {
2800                    continue;
2801                }
2802                let fx = &self.fields[jobs[jx].1.i];
2803                for (y, &jy) in js.iter().enumerate().skip(x + 1) {
2804                    if used[y] {
2805                        continue;
2806                    }
2807                    let fy = &self.fields[jobs[jy].1.i];
2808                    if fx.desc.on != fy.desc.to[0] || fx.desc.to[0] != fy.desc.on {
2809                        continue;
2810                    }
2811                    if fx.desc.max_distance != fy.desc.max_distance {
2812                        continue;
2813                    }
2814                    let (Some(m1), Some(m2)) =
2815                        (jobs[jx].1.near_margins[0], jobs[jy].1.near_margins[0])
2816                    else {
2817                        continue;
2818                    };
2819                    let margin = m1.max(m2);
2820                    let ea = self.entry(fx.desc.on)?;
2821                    let eb = self.entry(fx.desc.to[0])?;
2822                    let mut near_a = Vec::new();
2823                    let mut near_b = Vec::new();
2824                    fast_mesh_query::triangles_near(
2825                        &ea.mesh,
2826                        &ea.bvh,
2827                        &eb.mesh,
2828                        &eb.bvh,
2829                        &jobs[jx].1.b_to_a[0],
2830                        round_up(margin),
2831                        &mut near_a,
2832                        &mut near_b,
2833                    )?;
2834                    jobs[jx].1.shared_near = Some(near_a);
2835                    jobs[jy].1.shared_near = Some(near_b);
2836                    #[cfg(test)]
2837                    counter::add_shared();
2838                    used[x] = true;
2839                    used[y] = true;
2840                    break;
2841                }
2842            }
2843        }
2844        Ok(())
2845    }
2846
2847    /// Brings what field `i` reads up to date, the part of an update that
2848    /// needs the meshes mutably: its BVHs (the targets' always, A's when a
2849    /// near set is needed) and the targets' sign data. Then decides what the
2850    /// update recomputes (see the module docs). Writes no field state.
2851    fn prepare_field(&mut self, i: usize) -> Result<FieldJob> {
2852        let job = self.prepare_job(i)?;
2853        let mode = self.fields[i].desc.sign;
2854        for k in 0..self.fields[i].desc.to.len() {
2855            // A non-uniformly scaled target is baked into the frame (see
2856            // `FieldJob::temp_targets`): its real sign data are unused.
2857            if job.scaled && job.temp_targets[k].is_some() {
2858                continue;
2859            }
2860            let f = &mut self.fields[i];
2861            let eb = self
2862                .entries
2863                .get_mut(f.desc.to[k])
2864                .ok_or(SceneError::UnknownMesh)?;
2865            if f.sign[k]
2866                .as_ref()
2867                .is_some_and(|s| s.is_current(&eb.mesh, &eb.bvh))
2868            {
2869                f.sign_builds[k] = None;
2870                continue;
2871            }
2872            // A position-only edit refits around the dirty vertices; anything
2873            // else rebuilds over the whole mesh.
2874            let id = f.id;
2875            let refitted = match (
2876                &mut f.sign[k],
2877                eb.hook(id).and_then(FieldHook::refit_changes),
2878            ) {
2879                (Some(s), Some(changes)) => s.try_refit(&eb.mesh, &eb.bvh, changes)?,
2880                _ => false,
2881            };
2882            if !refitted {
2883                let f = &mut self.fields[i];
2884                let eb = self
2885                    .entries
2886                    .get_mut(f.desc.to[k])
2887                    .ok_or(SceneError::UnknownMesh)?;
2888                f.sign[k] = Some(SignData::build(&mut eb.mesh, &eb.bvh, mode)?);
2889            }
2890            // Current now: an unfinished build of it is of no use.
2891            self.fields[i].sign_builds[k] = None;
2892        }
2893        Ok(job)
2894    }
2895
2896    /// The near margin of field `i`'s next interaction update: what its last
2897    /// left, or at first a fraction of the diagonal of A's box.
2898    fn live_margin(&self, i: usize) -> Result<f64> {
2899        let margin = self.fields[i].live.margin;
2900        if margin > 0.0 {
2901            return Ok(margin);
2902        }
2903        let ea = self.entry(self.fields[i].desc.on)?;
2904        let diagonal = ea
2905            .bvh
2906            .root_bounds()
2907            .filter(|b| !b.is_empty())
2908            .map_or(0.0, |b| {
2909                let d = [0, 1, 2].map(|c| f64::from(b.max[c]) - f64::from(b.min[c]));
2910                (d[0] * d[0] + d[1] * d[1] + d[2] * d[2]).sqrt()
2911            });
2912        Ok(LIVE_FIRST_MARGIN * diagonal)
2913    }
2914
2915    /// Field `i`'s `LiveState::travel` once its targets are at `b_to_a`: a
2916    /// point of A has moved relative to a target by at most the farthest move
2917    /// of a corner of A's box (a rigid move is affine, and the box holds A).
2918    fn live_travel(&self, i: usize, b_to_a: &[Rigid]) -> Result<f64> {
2919        let f = &self.fields[i];
2920        let mut step = 0.0f64;
2921        if f.live.poses.len() == b_to_a.len()
2922            && let Some(b) = self
2923                .entry(f.desc.on)?
2924                .bvh
2925                .root_bounds()
2926                .filter(|b| !b.is_empty())
2927        {
2928            for (old, new) in f.live.poses.iter().zip(b_to_a) {
2929                let (old, new) = (old.inverse(), new.inverse());
2930                for k in 0..8 {
2931                    let corner =
2932                        std::array::from_fn(|d| if k >> d & 1 == 0 { b.min[d] } else { b.max[d] });
2933                    let (p, q) = (old.apply(corner), new.apply(corner));
2934                    let d: f64 = (0..3).map(|c| (p[c] - q[c]) * (p[c] - q[c])).sum();
2935                    step = step.max(d.sqrt());
2936                }
2937            }
2938        }
2939        Ok(f.live.travel + step)
2940    }
2941
2942    /// [`Scene::prepare_field`] but the sign data: the BVHs, then what the
2943    /// update recomputes. A step unit builds the sign data in its steps.
2944    fn prepare_job(&mut self, i: usize) -> Result<FieldJob> {
2945        let (id, on) = (self.fields[i].id, self.fields[i].desc.on);
2946        let mut to = Vec::new();
2947        to.try_reserve_exact(self.fields[i].desc.to.len() + 1)
2948            .map_err(|_| oom())?;
2949        to.extend_from_slice(&self.fields[i].desc.to);
2950        let n = to.len();
2951        let md = self.fields[i].desc.max_distance;
2952        let hook = |m: MeshId| -> Result<FieldHook> {
2953            self.entry(m)?
2954                .hook(id)
2955                .cloned()
2956                .ok_or(SceneError::Corrupted)
2957        };
2958        let hook_a = hook(on)?;
2959        if self.is_scaled_field(on, &to)? {
2960            // A scaled placement is involved: the rigid maps below do not
2961            // exist, so the update recomputes every vertex through the
2962            // placements (see `FieldJob::scaled`). Only the targets' BVHs
2963            // are needed (A's takes no part); their real sign data are
2964            // built in `prepare_field` (or the step unit) except for the
2965            // non-uniform targets baked here.
2966            self.prepare(Some(&to))?;
2967            return self.prepare_scaled_job(i, on, to, hook_a);
2968        }
2969        let mut whole = self.fields[i].fresh || hook_a.changes.is_none();
2970        let mut moved = Vec::new();
2971        moved.try_reserve_exact(n).map_err(|_| oom())?;
2972        for &m in &to {
2973            let hook_b = hook(m)?;
2974            whole |= hook_b.changes.is_none();
2975            moved.push(
2976                hook_a.placement_changed
2977                    || hook_b.placement_changed
2978                    || hook_b.changes.as_ref().is_some_and(|c| !c.is_empty()),
2979            );
2980        }
2981        // The interaction tier takes what is not due in full for itself.
2982        let live = !whole
2983            && self
2984                .field_interaction
2985                .is_some_and(|points| self.fields[i].finite.len() > points);
2986        let full = !live && (whole || md.is_infinite() || self.fields[i].live.stale);
2987        // The targets' BVHs are always needed; A's only for a near set.
2988        let need_near = !full && !live && moved.contains(&true);
2989        // The tier queries A's BVH around an edit of a target.
2990        let edited = live
2991            && to.iter().any(|&m| {
2992                self.entry(m)
2993                    .ok()
2994                    .and_then(|e| e.hook(self.fields[i].id))
2995                    .and_then(|h| h.changes.as_ref())
2996                    .is_some_and(|c| !c.is_empty())
2997            });
2998        if need_near || edited {
2999            to.push(on);
3000        }
3001        self.prepare(Some(&to))?;
3002        to.truncate(n);
3003        let mut near_margins = Vec::new();
3004        near_margins.try_reserve_exact(n).map_err(|_| oom())?;
3005        for (k, &m) in to.iter().enumerate() {
3006            near_margins.push(if need_near && moved[k] {
3007                Some(pad(md, self.pair_magnitude(on, m)?))
3008            } else {
3009                None
3010            });
3011        }
3012        let mut b_to_a = Vec::new();
3013        b_to_a.try_reserve_exact(n).map_err(|_| oom())?;
3014        for &m in &to {
3015            b_to_a.push(self.relative_transform(on, m)?);
3016        }
3017        let live_travel = if live {
3018            self.live_travel(i, &b_to_a)?
3019        } else {
3020            0.0
3021        };
3022        let ma = &self.entry(on)?.mesh;
3023        let changes = hook_a.changes;
3024        let topology = changes.as_ref().is_some_and(ChangeSet::topology_changed);
3025        let work = if full {
3026            ma.vertex_slots().saturating_mul(n)
3027        } else {
3028            let listed = changes.as_ref().map_or(0, |c| {
3029                c.vertices_added.len() + c.vertices_moved.len() + c.vertices_removed.len()
3030            });
3031            let known = if live {
3032                self.field_interaction.unwrap_or(0)
3033            } else if moved.contains(&true) {
3034                self.fields[i].finite.len()
3035            } else {
3036                0
3037            };
3038            let mut near = 0;
3039            for (k, &m) in to.iter().enumerate() {
3040                if let Some(margin) = near_margins[k]
3041                    && self.roots_within(on, m, &b_to_a[k], margin)?
3042                {
3043                    near += ma.triangle_count();
3044                }
3045            }
3046            let flips = if topology { ma.vertex_slots() } else { 0 };
3047            (listed + known).saturating_mul(n) + near + flips
3048        };
3049        Ok(FieldJob {
3050            i,
3051            full,
3052            moved,
3053            topology,
3054            changes,
3055            near_margins,
3056            b_to_a,
3057            near_limit: near_pair_limit(ma.vertex_slots()),
3058            work,
3059            shared_near: None,
3060            live,
3061            live_travel,
3062            scaled: false,
3063            frame_scale: 1.0,
3064            temp_targets: Vec::new(),
3065        })
3066    }
3067
3068    /// Whether the field's update runs scaled: A's placement or any target's
3069    /// is scaled.
3070    fn is_scaled_field(&self, on: MeshId, to: &[MeshId]) -> Result<bool> {
3071        if self.entry(on)?.placement.is_scaled() {
3072            return Ok(true);
3073        }
3074        for &m in to {
3075            if self.entry(m)?.placement.is_scaled() {
3076                return Ok(true);
3077            }
3078        }
3079        Ok(false)
3080    }
3081
3082    /// Whether the update of field `i` bakes target `k` (a non-uniformly
3083    /// scaled target, whose real sign data are unused).
3084    fn target_uses_temp(&self, i: usize, k: usize) -> bool {
3085        self.entry(self.fields[i].desc.to[k])
3086            .is_ok_and(|e| e.placement.is_scaled() && e.placement.uniform_scale().is_none())
3087    }
3088
3089    /// [`Scene::prepare_job`] for a scaled field: every vertex is
3090    /// recomputed through the placements, on baked copies of the
3091    /// non-uniformly scaled targets. `hook_a` is A's merged changes.
3092    fn prepare_scaled_job(
3093        &mut self,
3094        i: usize,
3095        on: MeshId,
3096        to: Vec<MeshId>,
3097        hook_a: FieldHook,
3098    ) -> Result<FieldJob> {
3099        let n = to.len();
3100        let pa = self.entry(on)?.placement;
3101        // The frame the update measures in: A's local space when A is rigid
3102        // or uniformly scaled (frame distances times `frame_scale` are world
3103        // distances), else the world. Non-uniform targets are baked into it.
3104        let in_a_frame = pa.uniform_scale().is_some();
3105        let frame_scale = pa.uniform_scale().unwrap_or(1.0);
3106        let mut temp_targets: Vec<Option<TempTarget>> = Vec::new();
3107        temp_targets.try_reserve_exact(n).map_err(|_| oom())?;
3108        for &m in &to {
3109            let (pb, ob, options) = {
3110                let eb = self.entry(m)?;
3111                (eb.placement, eb.mesh.origin(), *eb.bvh.options())
3112            };
3113            if pb.is_scaled() && pb.uniform_scale().is_none() {
3114                let map = |l: [f32; 3]| {
3115                    let full = [
3116                        ob[0] + f64::from(l[0]),
3117                        ob[1] + f64::from(l[1]),
3118                        ob[2] + f64::from(l[2]),
3119                    ];
3120                    if in_a_frame {
3121                        pa.inverse_apply(pb.apply(full))
3122                    } else {
3123                        pb.apply(full)
3124                    }
3125                };
3126                let eb = self.entry(m)?;
3127                let (mut mesh, bvh) = baked_mesh(&eb.mesh, map, &options)?;
3128                let mode = self.fields[i].desc.sign;
3129                let sign = SignData::build(&mut mesh, &bvh, mode)?;
3130                temp_targets.push(Some(TempTarget { mesh, bvh, sign }));
3131            } else {
3132                temp_targets.push(None);
3133            }
3134        }
3135        let ma = &self.entry(on)?.mesh;
3136        let changes = hook_a.changes;
3137        let topology = changes.as_ref().is_some_and(ChangeSet::topology_changed);
3138        let slots = ma.vertex_slots();
3139        let mut moved = Vec::new();
3140        moved.try_reserve_exact(n).map_err(|_| oom())?;
3141        moved.resize(n, true);
3142        Ok(FieldJob {
3143            i,
3144            full: true,
3145            moved,
3146            topology,
3147            changes,
3148            near_margins: Vec::new(),
3149            b_to_a: Vec::new(),
3150            near_limit: near_pair_limit(slots),
3151            // Every vertex against every target, plus one baked copy each.
3152            work: slots.saturating_mul(n),
3153            shared_near: None,
3154            live: false,
3155            live_travel: 0.0,
3156            scaled: true,
3157            frame_scale,
3158            temp_targets,
3159        })
3160    }
3161
3162    /// Computes the prepared fields without writing anything: one result
3163    /// per job, in job order. With the `parallel` feature, when the jobs'
3164    /// work adds up to [`PAR_MIN_FIELD_WORK`] or more, the fields are
3165    /// computed in parallel, one job per task.
3166    ///
3167    /// # Errors
3168    /// `OutOfMemory` when the per-job results cannot be allocated; every
3169    /// field then stays as it was.
3170    fn compute_fields(&self, jobs: &[(usize, FieldJob)]) -> Result<Vec<Result<FieldPlan>>> {
3171        let threshold = field_threshold(jobs.iter().map(|(_, job)| job));
3172        // Chunks of one job; a field's error is its own result.
3173        par::map_chunks(jobs, 1, threshold, oom, |_, chunk| {
3174            Ok(self.compute_field(&chunk[0].1))
3175        })
3176    }
3177
3178    /// Computes the new values of a prepared field and what changes with
3179    /// them, reading the meshes only: the vertices to recompute (searching
3180    /// A's triangles near B when needed), their distances, and the field's
3181    /// new bookkeeping. A step unit runs the same three parts, the middle
3182    /// one in pieces.
3183    fn compute_field(&self, job: &FieldJob) -> Result<FieldPlan> {
3184        let pick = self.pick_candidates(job)?;
3185        let mut values = Vec::with_capacity(pick.candidates.len());
3186        let mut nearest = Vec::with_capacity(pick.candidates.len());
3187        let recomputed = self.compute_values(job, &pick.candidates, &mut values, &mut nearest)?;
3188        self.finish_field(job, pick, values, nearest, recomputed)
3189    }
3190
3191    /// The vertices a prepared field's update recomputes, reading the
3192    /// meshes only.
3193    fn pick_candidates(&self, job: &FieldJob) -> Result<Pick> {
3194        if job.scaled {
3195            // Scaled updates recompute every vertex through the placements
3196            // (see `compute_values_scaled`), with no near search.
3197            return self.assemble_pick(job, &[], true, false);
3198        }
3199        let f = &self.fields[job.i];
3200        let ea = self.entry(f.desc.on)?;
3201        let ma = &ea.mesh;
3202
3203        // A shared pair query with the reciprocal field already found A's
3204        // near triangles: no search here.
3205        if let Some(shared) = &job.shared_near {
3206            return self.assemble_pick(job, shared, job.full, false);
3207        }
3208        // A's triangles near each moved target, as `Scene::triangles_near`
3209        // (with the BVHs `prepare_field` brought up to date), unless one
3210        // search finds too many pairs: then every vertex is recomputed.
3211        let mut near = Vec::new();
3212        let mut all = job.full;
3213        let mut aborted = false;
3214        for (k, margin) in job.near_margins.iter().enumerate() {
3215            let Some(margin) = margin.filter(|_| !all) else {
3216                continue;
3217            };
3218            let eb = self.entry(f.desc.to[k])?;
3219            aborted = !fast_mesh_query::triangles_near_limited(
3220                ma,
3221                &ea.bvh,
3222                &eb.mesh,
3223                &eb.bvh,
3224                &job.b_to_a[k],
3225                round_up(margin),
3226                job.near_limit,
3227                &mut near,
3228            )?;
3229            all = aborted;
3230        }
3231        self.assemble_pick(job, &near, all, aborted)
3232    }
3233
3234    /// The vertices a prepared field's update recomputes, given A's
3235    /// triangles `near` the moved targets, whether `all` are, and whether a
3236    /// near search passed its limit. Reads the meshes only.
3237    fn assemble_pick(
3238        &self,
3239        job: &FieldJob,
3240        near: &[TriangleId],
3241        all: bool,
3242        aborted: bool,
3243    ) -> Result<Pick> {
3244        if job.live {
3245            return self.assemble_live_pick(job);
3246        }
3247        let f = &self.fields[job.i];
3248        let ma = &self.entry(f.desc.on)?.mesh;
3249        let slots = ma.vertex_slots();
3250        let defined = |v: VertexId| ma.is_vertex_alive(v) && ma.corner_count(v) > 0;
3251
3252        // The vertices to recompute, ascending.
3253        let candidates: Vec<VertexId> = if all {
3254            (0..slots as VertexId).collect()
3255        } else {
3256            let mut c = Vec::new();
3257            for &t in near {
3258                if let Some(tri) = ma.triangle(t) {
3259                    c.extend_from_slice(&tri);
3260                }
3261            }
3262            if job.geometric() {
3263                // The finite values that came from a moved target.
3264                let from_moved = |v: VertexId| match f.nearest.get(v as usize) {
3265                    Some(&k) => job.moved.get(k as usize).copied().unwrap_or(true),
3266                    None => true,
3267                };
3268                c.extend(f.finite.iter().copied().filter(|&v| from_moved(v)));
3269            }
3270            if let Some(ch) = &job.changes {
3271                c.extend_from_slice(&ch.vertices_added);
3272                c.extend_from_slice(&ch.vertices_moved);
3273                c.extend_from_slice(&ch.vertices_removed);
3274            }
3275            if job.topology {
3276                for v in 0..slots as VertexId {
3277                    let was = f.referenced.get(v as usize).copied().unwrap_or(false);
3278                    if was != defined(v) {
3279                        c.push(v);
3280                    }
3281                }
3282            }
3283            c.sort_unstable();
3284            c.dedup();
3285            c
3286        };
3287        Ok(Pick {
3288            candidates,
3289            all,
3290            aborted,
3291            margin: 0.0,
3292        })
3293    }
3294
3295    /// The vertices an update of the interaction tier recomputes, from the
3296    /// values alone (no pair search: one pass over the slots, whose cost does
3297    /// not depend on how many triangles are close):
3298    ///
3299    /// - the near vertices. A distance changes no faster than the motion, so
3300    ///   a vertex can now be within the band only when its value, less how
3301    ///   far the meshes moved since it was computed, is. Over the budget, the
3302    ///   smallest such first, and the next band is the last one taken. So
3303    ///   the contact stays exact within the band, and a vertex farther away
3304    ///   comes up as the motion adds up;
3305    /// - what a target's edit may have changed the most: A's triangles in the
3306    ///   box of its moved vertices (widened by the band), spread over them up
3307    ///   to [`LIVE_EDIT_SHARE`] of the budget;
3308    /// - A's own added and removed vertices, those whose "used by a triangle"
3309    ///   state flipped, and those it moved, the closest to the targets first,
3310    ///   up to the same share (the others differ by no more than they moved);
3311    /// - the sweep (see [`LIVE_SWEEP_SHARE`]).
3312    fn assemble_live_pick(&self, job: &FieldJob) -> Result<Pick> {
3313        let f = &self.fields[job.i];
3314        let ea = self.entry(f.desc.on)?;
3315        let ma = &ea.mesh;
3316        let slots = ma.vertex_slots();
3317        let defined = |v: VertexId| ma.is_vertex_alive(v) && ma.corner_count(v) > 0;
3318        let values = ma.channel(&f.channel).ok_or(SceneError::Corrupted)?;
3319        let points = self.field_interaction.unwrap_or(1);
3320        let margin = self.live_margin(job.i)?;
3321        let md = f.desc.max_distance;
3322        let stamps = &f.live.stamps;
3323        // Less than the value by how far it may have moved; far values count
3324        // as `max_distance`, the nearest they can be.
3325        let reach = |v: usize| -> f64 {
3326            let moved = stamps
3327                .get(v)
3328                .map_or(job.live_travel, |&t| job.live_travel - f64::from(t)); // No stamp yet: computed at 0.
3329            f64::from(values[v].abs()).min(md) - moved
3330        };
3331        let mut c: Vec<VertexId> = (0..values.len().min(slots))
3332            .filter(|&v| !values[v].is_nan() && reach(v) <= margin)
3333            .map(|v| v as VertexId)
3334            .collect();
3335        let mut next = margin;
3336        if c.len() > points {
3337            let key = |&v: &VertexId| (reach(v as usize), v);
3338            let (_, last, _) = c.select_nth_unstable_by(points - 1, |x, y| {
3339                let (a, b) = (key(x), key(y));
3340                a.0.total_cmp(&b.0).then(a.1.cmp(&b.1))
3341            });
3342            let band = reach(*last as usize);
3343            c.truncate(points);
3344            // Overdue vertices take the budget: the band itself holds.
3345            if band > 0.0 {
3346                next = band;
3347            }
3348        } else if c.len() < points / 2 {
3349            next = margin * LIVE_GROW;
3350        }
3351        let edits = (points / LIVE_EDIT_SHARE).max(1);
3352        let pad = margin as f32;
3353        let mut boxed = Vec::new();
3354        for (k, &m) in f.desc.to.iter().enumerate() {
3355            let eb = self.entry(m)?;
3356            let Some(ch) = eb.hook(f.id).and_then(|h| h.changes.as_ref()) else {
3357                continue;
3358            };
3359            let (mut lo, mut hi) = ([f32::INFINITY; 3], [f32::NEG_INFINITY; 3]);
3360            for &v in ch.vertices_moved.iter().chain(&ch.vertices_added) {
3361                if let Some(p) = eb.mesh.position(v) {
3362                    let q = job.b_to_a[k].apply(p);
3363                    for d in 0..3 {
3364                        lo[d] = lo[d].min(q[d] as f32 - pad);
3365                        hi[d] = hi[d].max(q[d] as f32 + pad);
3366                    }
3367                }
3368            }
3369            if lo[0] <= hi[0] {
3370                boxed.clear();
3371                ea.bvh.query_aabb(ma, lo, hi, &mut boxed);
3372                boxed.sort_unstable();
3373                let every = boxed.len().div_ceil(edits).max(1);
3374                let tris = boxed.iter().step_by(every).filter_map(|&t| ma.triangle(t));
3375                c.extend(tris.flatten());
3376            }
3377        }
3378        if let Some(ch) = &job.changes {
3379            c.extend_from_slice(&ch.vertices_added);
3380            c.extend_from_slice(&ch.vertices_removed);
3381            // A brush moves many vertices a little, and a vertex far from
3382            // the targets costs many times one near them: those closest to
3383            // the contact, within the budget.
3384            let mut moved = ch.vertices_moved.clone();
3385            if moved.len() > edits {
3386                let key = |&v: &VertexId| (values.get(v as usize).map_or(0.0, |x| x.abs()), v);
3387                moved.select_nth_unstable_by(edits - 1, |x, y| {
3388                    let (a, b) = (key(x), key(y));
3389                    a.0.total_cmp(&b.0).then(a.1.cmp(&b.1))
3390                });
3391                moved.truncate(edits);
3392            }
3393            c.extend_from_slice(&moved);
3394        }
3395        if job.topology {
3396            for v in 0..slots as VertexId {
3397                let was = f.referenced.get(v as usize).copied().unwrap_or(false);
3398                if was != defined(v) {
3399                    c.push(v);
3400                }
3401            }
3402        }
3403        let (start, count) = self.live_sweep(job.i, slots);
3404        c.extend((0..count).map(|k| ((start as usize + k) % slots) as VertexId));
3405        c.sort_unstable();
3406        c.dedup();
3407        Ok(Pick {
3408            candidates: c,
3409            all: false,
3410            aborted: false,
3411            margin: next,
3412        })
3413    }
3414
3415    /// The sweep of field `i` over `slots` vertex slots, this update: its
3416    /// first slot and how many it visits (see [`LIVE_SWEEP_SHARE`]).
3417    fn live_sweep(&self, i: usize, slots: usize) -> (u32, usize) {
3418        if slots == 0 {
3419            return (0, 0);
3420        }
3421        let count = self
3422            .field_interaction
3423            .map_or(0, |points| (points / LIVE_SWEEP_SHARE).max(1))
3424            .min(slots);
3425        (self.fields[i].live.sweep % slots as u32, count)
3426    }
3427
3428    /// Appends the new values of `candidates`, a run of a prepared field's
3429    /// candidates, to `values`, and the targets they came from to
3430    /// `nearest`, reading the meshes only. Every value depends only on its
3431    /// own vertex, so computing the candidates in runs gives the same bits
3432    /// as all at once. Returns the points the kernel ran on.
3433    fn compute_values(
3434        &self,
3435        job: &FieldJob,
3436        candidates: &[VertexId],
3437        values: &mut Vec<f32>,
3438        nearest: &mut Vec<u32>,
3439    ) -> Result<usize> {
3440        if job.scaled {
3441            return self.compute_values_scaled(job, candidates, values, nearest);
3442        }
3443        let f = &self.fields[job.i];
3444        let ma = &self.entry(f.desc.on)?.mesh;
3445        let defined = |v: VertexId| ma.is_vertex_alive(v) && ma.corner_count(v) > 0;
3446        #[cfg(test)]
3447        if counter::fails(f.id) {
3448            return Err(oom());
3449        }
3450        let mut targets = Vec::new();
3451        targets
3452            .try_reserve_exact(f.desc.to.len())
3453            .map_err(|_| oom())?;
3454        for (k, &m) in f.desc.to.iter().enumerate() {
3455            let eb = self.entry(m)?;
3456            targets.push(Target {
3457                mesh: &eb.mesh,
3458                bvh: &eb.bvh,
3459                sign: f.sign[k].as_ref().ok_or(SceneError::Corrupted)?,
3460                // A's points go into each target's local space in f64, then
3461                // are rounded to f32 (§5.2).
3462                to_local: job.b_to_a[k].inverse(),
3463            });
3464        }
3465
3466        let mut ids = Vec::new();
3467        let mut points = Vec::new();
3468        for &v in candidates {
3469            if let Some(p) = ma.position(v).filter(|_| defined(v)) {
3470                ids.push(v);
3471                points.push(p);
3472            }
3473        }
3474        let mut out = vec![0.0f32; points.len()];
3475        let mut from = vec![0u32; points.len()];
3476        let md = f.desc.max_distance;
3477        signed_distances_multi(&targets, &points, md, &mut out, &mut from)?;
3478
3479        let mut k = 0;
3480        for &v in candidates {
3481            let (x, n) = if ids.get(k) == Some(&v) {
3482                k += 1;
3483                (out[k - 1], from[k - 1])
3484            } else {
3485                (f32::NAN, u32::MAX)
3486            };
3487            values.push(x);
3488            nearest.push(n);
3489        }
3490        Ok(points.len())
3491    }
3492
3493    /// Appends the new values of `candidates` like [`Scene::compute_values`]
3494    /// for a scaled field: every candidate is measured through the
3495    /// placements, one target batch at a time.
3496    ///
3497    /// The frame the candidates are measured in is A's local space when A
3498    /// is rigid or uniformly scaled (frame distances times
3499    /// [`FieldJob::frame_scale`] are world distances), else the world. Each
3500    /// target is measured in its own local space, where the kernel runs:
3501    /// - a rigid target: the frame point goes to the world and back into the
3502    ///   target's local space; the kernel's bound and values are world ones;
3503    /// - a uniformly scaled target: the same, through the target's inverse;
3504    ///   the kernel runs with the world bound divided by the scale and its
3505    ///   values are multiplied back (a uniform scale multiplies every
3506    ///   distance, and signs are preserved by a positive scale);
3507    /// - a non-uniformly scaled target: its baked copy in
3508    ///   [`FieldJob::temp_targets`] already lives in the frame, so the frame
3509    ///   points go in directly and the values are times `frame_scale`.
3510    ///
3511    /// The per-vertex value is the minimum over the targets, ties going to
3512    /// the lowest target index, with magnitudes past `max_distance`
3513    /// (compared in `f64`) reading `+∞` and no target — the same bits as
3514    /// [`signed_distances_multi`] over the same local points.
3515    fn compute_values_scaled(
3516        &self,
3517        job: &FieldJob,
3518        candidates: &[VertexId],
3519        values: &mut Vec<f32>,
3520        nearest: &mut Vec<u32>,
3521    ) -> Result<usize> {
3522        let f = &self.fields[job.i];
3523        #[cfg(test)]
3524        if counter::fails(f.id) {
3525            return Err(oom());
3526        }
3527        if f.desc.to.len() >= u32::MAX as usize {
3528            return Err(SceneError::InvalidInput("too many targets"));
3529        }
3530        let ea = self.entry(f.desc.on)?;
3531        let (pa, oa) = (ea.placement, ea.mesh.origin());
3532        let in_a_frame = pa.uniform_scale().is_some();
3533        let md = f.desc.max_distance;
3534        // The candidates' frame points in f64 (`None` for dead or unused
3535        // vertices, which read NaN), and the positions of the defined ones.
3536        let mut frame: Vec<Option<[f64; 3]>> = Vec::new();
3537        frame
3538            .try_reserve_exact(candidates.len())
3539            .map_err(|_| oom())?;
3540        for &v in candidates {
3541            frame.push(
3542                ea.mesh
3543                    .position(v)
3544                    .filter(|_| ea.mesh.is_vertex_alive(v) && ea.mesh.corner_count(v) > 0)
3545                    .map(|p| {
3546                        if in_a_frame {
3547                            [f64::from(p[0]), f64::from(p[1]), f64::from(p[2])]
3548                        } else {
3549                            pa.apply([
3550                                oa[0] + f64::from(p[0]),
3551                                oa[1] + f64::from(p[1]),
3552                                oa[2] + f64::from(p[2]),
3553                            ])
3554                        }
3555                    }),
3556            );
3557        }
3558        let mut at: Vec<usize> = Vec::new();
3559        at.try_reserve_exact(candidates.len()).map_err(|_| oom())?;
3560        let mut fpts: Vec<[f64; 3]> = Vec::new();
3561        fpts.try_reserve_exact(candidates.len())
3562            .map_err(|_| oom())?;
3563        for (ci, q) in frame.iter().enumerate() {
3564            if let Some(q) = q {
3565                at.push(ci);
3566                fpts.push(*q);
3567            }
3568        }
3569        let mut best: Vec<(f32, u32)> = Vec::new();
3570        best.try_reserve_exact(candidates.len())
3571            .map_err(|_| oom())?;
3572        best.resize(candidates.len(), (f32::INFINITY, u32::MAX));
3573        for (k, &m) in f.desc.to.iter().enumerate() {
3574            // The measuring space, its kernel bound (in its own units) and
3575            // what its values are multiplied by to read world distances.
3576            let eb = self.entry(m)?;
3577            let (mesh, bvh, sign, bound, to_world) = match &job.temp_targets[k] {
3578                Some(t) => (
3579                    &t.mesh,
3580                    &t.bvh,
3581                    &t.sign,
3582                    round_up(md / job.frame_scale),
3583                    job.frame_scale,
3584                ),
3585                None => {
3586                    let s = eb.placement.uniform_scale().unwrap_or(1.0);
3587                    (
3588                        &eb.mesh,
3589                        &eb.bvh,
3590                        f.sign[k].as_ref().ok_or(SceneError::Corrupted)?,
3591                        round_up(md / s),
3592                        s,
3593                    )
3594                }
3595            };
3596            let (pb, ob) = (eb.placement, eb.mesh.origin());
3597            let temp = job.temp_targets[k].is_some();
3598            let mut points: Vec<[f32; 3]> = Vec::new();
3599            points.try_reserve_exact(fpts.len()).map_err(|_| oom())?;
3600            for &q in &fpts {
3601                // Into the target's measuring space: a baked copy lives in
3602                // the frame already, so its points go in directly; a real
3603                // target's go to the world first (A's placement), then back
3604                // into its stored local space (the placement inverse, minus
3605                // its origin).
3606                let l = if temp {
3607                    q
3608                } else {
3609                    let w = if in_a_frame {
3610                        pa.apply([oa[0] + q[0], oa[1] + q[1], oa[2] + q[2]])
3611                    } else {
3612                        q
3613                    };
3614                    let m = pb.inverse_apply(w);
3615                    [m[0] - ob[0], m[1] - ob[1], m[2] - ob[2]]
3616                };
3617                points.push([l[0] as f32, l[1] as f32, l[2] as f32]);
3618            }
3619            let mut out: Vec<f32> = Vec::new();
3620            out.try_reserve_exact(points.len()).map_err(|_| oom())?;
3621            out.resize(points.len(), 0.0);
3622            signed_distances(mesh, bvh, sign, &points, bound, &mut out)?;
3623            for (j, &ci) in at.iter().enumerate() {
3624                let w = to_world * f64::from(out[j]);
3625                let x = if w.abs() > md {
3626                    f32::INFINITY
3627                } else {
3628                    w as f32
3629                };
3630                // Strictly smaller: a tie stays with the lower index.
3631                if x < best[ci].0 {
3632                    best[ci] = (x, k as u32);
3633                }
3634            }
3635        }
3636        values.try_reserve(candidates.len()).map_err(|_| oom())?;
3637        nearest.try_reserve(candidates.len()).map_err(|_| oom())?;
3638        for (ci, q) in frame.iter().enumerate() {
3639            let (x, n) = if q.is_some() {
3640                best[ci]
3641            } else {
3642                (f32::NAN, u32::MAX)
3643            };
3644            values.push(x);
3645            nearest.push(n);
3646        }
3647        Ok(fpts.len())
3648    }
3649
3650    /// The new state of a prepared field from its candidates, their new
3651    /// `values` and the targets those came from (in candidate order): what
3652    /// changed, and the field's new bookkeeping. Reads the meshes only.
3653    fn finish_field(
3654        &self,
3655        job: &FieldJob,
3656        pick: Pick,
3657        values: Vec<f32>,
3658        nearest: Vec<u32>,
3659        recomputed: usize,
3660    ) -> Result<FieldPlan> {
3661        let f = &self.fields[job.i];
3662        let ma = &self.entry(f.desc.on)?.mesh;
3663        let slots = ma.vertex_slots();
3664        let defined = |v: VertexId| ma.is_vertex_alive(v) && ma.corner_count(v) > 0;
3665        let Pick {
3666            candidates,
3667            all,
3668            aborted,
3669            margin,
3670        } = pick;
3671        if values.len() != candidates.len() || nearest.len() != candidates.len() {
3672            return Err(SceneError::Corrupted);
3673        }
3674
3675        let old = ma.channel(&f.channel).ok_or(SceneError::Corrupted)?;
3676        let changed: Vec<VertexId> = candidates
3677            .iter()
3678            .zip(&values)
3679            .filter(|&(&v, x)| old.get(v as usize).map(|o| o.to_bits()) != Some(x.to_bits()))
3680            .map(|(&v, _)| v)
3681            .collect();
3682        let referenced = (job.full || job.topology || f.referenced.len() != slots)
3683            .then(|| (0..slots as VertexId).map(defined).collect::<Vec<bool>>());
3684        let finite: Vec<VertexId> = {
3685            let now = candidates
3686                .iter()
3687                .zip(&values)
3688                .filter(|(_, x)| x.is_finite())
3689                .map(|(&v, _)| v);
3690            if job.live {
3691                // Unused until the update that ends the tier, which recomputes it.
3692                Vec::new()
3693            } else if all {
3694                now.collect()
3695            } else {
3696                let mut kept: Vec<VertexId> = f
3697                    .finite
3698                    .iter()
3699                    .copied()
3700                    .filter(|v| candidates.binary_search(v).is_err())
3701                    .chain(now)
3702                    .collect();
3703                kept.sort_unstable();
3704                kept
3705            }
3706        };
3707        Ok(FieldPlan {
3708            candidates,
3709            values,
3710            nearest,
3711            changed,
3712            referenced,
3713            finite,
3714            recomputed,
3715            aborted,
3716            margin,
3717        })
3718    }
3719
3720    /// Writes a computed field: its values, its colours, its reports and its
3721    /// bookkeeping. Fails, writing nothing, only when the field's channel or
3722    /// mesh is missing, or memory runs out.
3723    fn apply_field(&mut self, job: FieldJob, plan: FieldPlan) -> Result<()> {
3724        let i = job.i;
3725        if job.live {
3726            let slots = self.entry(self.fields[i].desc.on)?.mesh.vertex_slots();
3727            let (start, swept) = self.live_sweep(i, slots);
3728            let f = &mut self.fields[i];
3729            if slots > 0 {
3730                f.live.sweep = ((start as usize + swept) % slots) as u32;
3731            }
3732            f.live.margin = plan.margin;
3733            // Vertices left out keep the stamp of before this update's motion.
3734            f.live.stamps.resize(slots, f.live.travel as f32);
3735            f.live.travel = job.live_travel;
3736            let stamp = job.live_travel as f32;
3737            for &v in &plan.candidates {
3738                f.live.stamps[v as usize] = stamp;
3739            }
3740        }
3741        let (id, on) = (self.fields[i].id, self.fields[i].desc.on);
3742        let f = &mut self.fields[i];
3743        let ea = self.entries.get_mut(on).ok_or(SceneError::UnknownMesh)?;
3744        let slots = ea.mesh.vertex_slots();
3745        f.nearest
3746            .try_reserve(slots.saturating_sub(f.nearest.len()))
3747            .map_err(|_| oom())?;
3748        let channel = ea.mesh.derived_channel_mut(&f.channel)?;
3749        // Commit: nothing below fails.
3750        #[cfg(test)]
3751        {
3752            counter::add(plan.recomputed);
3753            counter::add_aborted(plan.aborted);
3754            counter::add_applied(id, plan.recomputed);
3755        }
3756        for (&v, &x) in plan.candidates.iter().zip(&plan.values) {
3757            channel[v as usize] = x;
3758        }
3759        f.nearest.resize(slots, u32::MAX);
3760        for (&v, &n) in plan.candidates.iter().zip(&plan.nearest) {
3761            f.nearest[v as usize] = n;
3762        }
3763        ea.reset_hook(id, false);
3764        if !plan.changed.is_empty() {
3765            ea.report_field(id);
3766        }
3767        if let Some(cs) = &mut ea.coloring
3768            && cs.coloring.field == id
3769        {
3770            let values = ea.mesh.channel(&cs.channel).unwrap_or(&[]);
3771            let dirty = cs.update(values, &plan.changed);
3772            ea.report_colors(Some(&dirty));
3773        }
3774        for (k, &m) in f.desc.to.iter().enumerate() {
3775            if let Some(eb) = self.entries.get_mut(m) {
3776                let baked = job.temp_targets.get(k).is_some_and(Option::is_some);
3777                eb.reset_hook(id, baked);
3778            }
3779        }
3780        f.fresh = false;
3781        f.computed = true;
3782        if job.live {
3783            f.live.stale = true;
3784        } else {
3785            f.live.stale = false;
3786            f.finite = plan.finite;
3787            f.live.travel = 0.0;
3788            f.live.stamps = Vec::new();
3789        }
3790        f.live.poses = job.b_to_a;
3791        if let Some(r) = plan.referenced {
3792            f.referenced = r;
3793        }
3794        Ok(())
3795    }
3796}
3797
3798/// The [`par::map_chunks`] threshold for computing `jobs` one field per
3799/// task: 0 (in parallel) with the `parallel` feature when there are several
3800/// and their work adds up to [`PAR_MIN_FIELD_WORK`] or more, else
3801/// `usize::MAX`.
3802fn field_threshold<'a>(jobs: impl Iterator<Item = &'a FieldJob>) -> usize {
3803    let (n, work) = jobs.fold((0, 0), |(n, w), job| (n + 1, w + job.work));
3804    let parallel =
3805        work >= PAR_MIN_FIELD_WORK && n > 1 && par::enabled() && !par::force_sequential();
3806    #[cfg(test)]
3807    if parallel {
3808        counter::add_parallel();
3809    }
3810    if parallel { 0 } else { usize::MAX }
3811}
3812
3813/// The most near pairs a field's near search may list for a mesh A of
3814/// `slots` vertex slots (see [`NEAR_PAIRS_PER_SLOT`]). Called on the thread
3815/// that runs the update, as the test override is per thread.
3816fn near_pair_limit(slots: usize) -> usize {
3817    #[cfg(test)]
3818    if let Some(limit) = counter::near_limit() {
3819        return limit;
3820    }
3821    slots.saturating_mul(NEAR_PAIRS_PER_SLOT)
3822}
3823
3824/// Test-only counts of the vertices field updates recompute, and of the
3825/// batches of fields computed in parallel.
3826#[cfg(test)]
3827pub(crate) mod counter {
3828    use std::cell::Cell;
3829
3830    // All are counted on the thread that runs the update (when fields are
3831    // applied), never on the pool's threads.
3832    thread_local! {
3833        static RECOMPUTED: Cell<usize> = const { Cell::new(0) };
3834        static PARALLEL: Cell<usize> = const { Cell::new(0) };
3835        static NEAR_LIMIT: Cell<Option<usize>> = const { Cell::new(None) };
3836        static ABORTED: Cell<usize> = const { Cell::new(0) };
3837        static FAILING: Cell<Option<super::FieldId>> = const { Cell::new(None) };
3838        static SIGN_STEPS: Cell<usize> = const { Cell::new(0) };
3839        static SHARED: Cell<usize> = const { Cell::new(0) };
3840    }
3841
3842    /// Counts one shared pair query between reciprocal fields.
3843    pub(crate) fn add_shared() {
3844        SHARED.with(|c| c.set(c.get() + 1));
3845    }
3846
3847    /// Shared pair queries on this thread since the last call.
3848    pub(crate) fn take_shared() -> usize {
3849        SHARED.with(|c| c.replace(0))
3850    }
3851
3852    /// Counts one step of a stepped sign data build.
3853    pub(crate) fn add_sign_step() {
3854        SIGN_STEPS.with(|c| c.set(c.get() + 1));
3855    }
3856
3857    /// Steps of stepped sign data builds on this thread since the last call.
3858    pub(crate) fn take_sign_steps() -> usize {
3859        SIGN_STEPS.with(|c| c.replace(0))
3860    }
3861
3862    /// Makes computing the values of `field` fail (out of memory) on this
3863    /// thread; `None`: nothing fails.
3864    pub(crate) fn set_failing(field: Option<super::FieldId>) {
3865        FAILING.with(|c| c.set(field));
3866    }
3867
3868    pub(crate) fn fails(field: super::FieldId) -> bool {
3869        FAILING.with(Cell::get) == Some(field)
3870    }
3871
3872    thread_local! {
3873        static APPLIED: std::cell::RefCell<Vec<(super::FieldId, usize)>> =
3874            const { std::cell::RefCell::new(Vec::new()) };
3875    }
3876
3877    /// Records that `field` was applied (its update written), having
3878    /// recomputed `points` vertices.
3879    pub(crate) fn add_applied(field: super::FieldId, points: usize) {
3880        APPLIED.with(|c| c.borrow_mut().push((field, points)));
3881    }
3882
3883    /// The fields applied on this thread since the last call, in order.
3884    pub(crate) fn take_applied() -> Vec<super::FieldId> {
3885        take_applied_counts().into_iter().map(|(f, _)| f).collect()
3886    }
3887
3888    /// As [`take_applied`], with the vertices each recomputed.
3889    pub(crate) fn take_applied_counts() -> Vec<(super::FieldId, usize)> {
3890        APPLIED.with(|c| std::mem::take(&mut *c.borrow_mut()))
3891    }
3892
3893    /// Counts one near search abandoned past its limit.
3894    pub(crate) fn add_aborted(aborted: bool) {
3895        ABORTED.with(|c| c.set(c.get() + usize::from(aborted)));
3896    }
3897
3898    /// Near searches abandoned on this thread since the last call.
3899    pub(crate) fn take_aborted() -> usize {
3900        ABORTED.with(|c| c.replace(0))
3901    }
3902
3903    /// Overrides the near-pair limit of field updates on this thread
3904    /// (`None`: the production limit).
3905    pub(crate) fn set_near_limit(limit: Option<usize>) {
3906        NEAR_LIMIT.with(|c| c.set(limit));
3907    }
3908
3909    pub(crate) fn near_limit() -> Option<usize> {
3910        NEAR_LIMIT.with(Cell::get)
3911    }
3912
3913    /// Counts `n` recomputed vertices.
3914    pub(crate) fn add(n: usize) {
3915        RECOMPUTED.with(|c| c.set(c.get() + n));
3916    }
3917
3918    /// Vertices recomputed on this thread since the last call.
3919    pub(crate) fn take() -> usize {
3920        RECOMPUTED.with(|c| c.replace(0))
3921    }
3922
3923    /// Counts one batch of fields computed in parallel.
3924    pub(crate) fn add_parallel() {
3925        PARALLEL.with(|c| c.set(c.get() + 1));
3926    }
3927
3928    /// Batches of fields computed in parallel on this thread since the last
3929    /// call.
3930    pub(crate) fn take_parallel() -> usize {
3931        PARALLEL.with(|c| c.replace(0))
3932    }
3933}
3934
3935#[cfg(test)]
3936mod tests {
3937    use fast_mesh_fields::{Colormap, OutOfRange, SignData, color_of, signed_distances};
3938    use fast_mesh_store::{Mesh, VertexId};
3939
3940    use super::*;
3941    use crate::query::tests::cube;
3942    use crate::{MeshOptions, Placement, Scene, SceneError, SceneOptions, UpdateMode};
3943
3944    /// A deterministic linear congruential generator.
3945    struct Lcg(u64);
3946
3947    impl Lcg {
3948        fn next(&mut self) -> f64 {
3949            self.0 = self
3950                .0
3951                .wrapping_mul(6364136223846793005)
3952                .wrapping_add(1442695040888963407);
3953            (self.0 >> 11) as f64 / (1u64 << 53) as f64
3954        }
3955
3956        fn range(&mut self, lo: f64, hi: f64) -> f64 {
3957            lo + (hi - lo) * self.next()
3958        }
3959
3960        fn below(&mut self, n: usize) -> usize {
3961            ((self.next() * n as f64) as usize).min(n - 1)
3962        }
3963    }
3964
3965    /// A flat `n` x `n` grid in the plane `z = 0`, `h` apart.
3966    fn sheet(n: u32, h: f32) -> Mesh {
3967        let mut pos = Vec::new();
3968        for y in 0..=n {
3969            for x in 0..=n {
3970                pos.extend_from_slice(&[x as f32 * h, y as f32 * h, 0.0]);
3971            }
3972        }
3973        let mut idx = Vec::new();
3974        for y in 0..n {
3975            for x in 0..n {
3976                let a = y * (n + 1) + x;
3977                let b = a + 1;
3978                let c = a + n + 1;
3979                idx.extend_from_slice(&[a, b, c, b, c + 1, c]);
3980            }
3981        }
3982        Mesh::from_buffers(&pos, &idx).unwrap()
3983    }
3984
3985    fn with_mode(mode: UpdateMode) -> MeshOptions {
3986        MeshOptions {
3987            mode,
3988            ..MeshOptions::default()
3989        }
3990    }
3991
3992    fn at(x: f64, y: f64, z: f64) -> Placement {
3993        Placement {
3994            translation: [x, y, z],
3995            ..Placement::IDENTITY
3996        }
3997    }
3998
3999    /// A rotation by `deg` degrees about z, then a translation.
4000    fn turned(deg: f64, t: [f64; 3]) -> Placement {
4001        let (s, c) = deg.to_radians().sin_cos();
4002        Placement {
4003            rotation: [[c, -s, 0.0], [s, c, 0.0], [0.0, 0.0, 1.0]],
4004            translation: t,
4005            ..Placement::IDENTITY
4006        }
4007    }
4008
4009    fn field(name: &str, on: MeshId, to: MeshId, max_distance: f64) -> DistanceField {
4010        DistanceField {
4011            name: name.to_owned(),
4012            on,
4013            to: vec![to],
4014            max_distance,
4015            sign: SignMode::Auto,
4016        }
4017    }
4018
4019    fn thick(name: &str, on: MeshId, max_distance: f64) -> ThicknessField {
4020        ThicknessField {
4021            name: name.to_owned(),
4022            on,
4023            max_distance,
4024        }
4025    }
4026
4027    fn curv(name: &str, on: MeshId) -> CurvatureField {
4028        CurvatureField {
4029            name: name.to_owned(),
4030            on,
4031        }
4032    }
4033
4034    /// A closed plate, 1 x 1 x 0.5 (`n` cells a side), facing out.
4035    fn plate(n: u32) -> Mesh {
4036        let h = 0.25;
4037        let w = n + 1;
4038        let mut pos = Vec::new();
4039        for z in [h, -h] {
4040            for y in 0..=n {
4041                for x in 0..=n {
4042                    pos.extend_from_slice(&[x as f32 / n as f32, y as f32 / n as f32, z]);
4043                }
4044            }
4045        }
4046        let t = |x: u32, y: u32| y * w + x;
4047        let b = |x: u32, y: u32| w * w + y * w + x;
4048        let mut idx = Vec::new();
4049        for y in 0..n {
4050            for x in 0..n {
4051                idx.extend_from_slice(&[
4052                    t(x, y),
4053                    t(x + 1, y),
4054                    t(x + 1, y + 1),
4055                    t(x, y),
4056                    t(x + 1, y + 1),
4057                    t(x, y + 1),
4058                    b(x, y),
4059                    b(x + 1, y + 1),
4060                    b(x + 1, y),
4061                    b(x, y),
4062                    b(x, y + 1),
4063                    b(x + 1, y + 1),
4064                ]);
4065            }
4066        }
4067        for x in 0..n {
4068            idx.extend_from_slice(&[
4069                t(x, 0),
4070                b(x, 0),
4071                b(x + 1, 0),
4072                t(x, 0),
4073                b(x + 1, 0),
4074                t(x + 1, 0),
4075                t(x, n),
4076                t(x + 1, n),
4077                b(x + 1, n),
4078                t(x, n),
4079                b(x + 1, n),
4080                b(x, n),
4081                t(0, x),
4082                t(0, x + 1),
4083                b(0, x + 1),
4084                t(0, x),
4085                b(0, x + 1),
4086                b(0, x),
4087                t(n, x),
4088                b(n, x),
4089                b(n, x + 1),
4090                t(n, x),
4091                b(n, x + 1),
4092                t(n, x + 1),
4093            ]);
4094        }
4095        Mesh::from_buffers(&pos, &idx).unwrap()
4096    }
4097
4098    /// The unit sphere from a twice-subdivided octahedron, facing out: 128
4099    /// triangles, every vertex about `1 / 1` of curvature.
4100    fn ball() -> Mesh {
4101        let mut pos: Vec<[f64; 3]> = vec![
4102            [1.0, 0.0, 0.0],
4103            [-1.0, 0.0, 0.0],
4104            [0.0, 1.0, 0.0],
4105            [0.0, -1.0, 0.0],
4106            [0.0, 0.0, 1.0],
4107            [0.0, 0.0, -1.0],
4108        ];
4109        let mut tris: Vec<[u32; 3]> = vec![
4110            [4, 0, 2],
4111            [4, 2, 1],
4112            [4, 1, 3],
4113            [4, 3, 0],
4114            [5, 2, 0],
4115            [5, 1, 2],
4116            [5, 3, 1],
4117            [5, 0, 3],
4118        ];
4119        for _ in 0..2 {
4120            let mut mid = std::collections::HashMap::new();
4121            let mut midpoint = |a: u32, b: u32, pos: &mut Vec<[f64; 3]>| {
4122                *mid.entry((a.min(b), a.max(b))).or_insert_with(|| {
4123                    let (pa, pb) = (pos[a as usize], pos[b as usize]);
4124                    pos.push(std::array::from_fn(|k| (pa[k] + pb[k]) / 2.0));
4125                    (pos.len() - 1) as u32
4126                })
4127            };
4128            let mut next = Vec::new();
4129            for [a, b, c] in tris {
4130                let ab = midpoint(a, b, &mut pos);
4131                let bc = midpoint(b, c, &mut pos);
4132                let ca = midpoint(c, a, &mut pos);
4133                next.extend([[a, ab, ca], [b, bc, ab], [c, ca, bc], [ab, bc, ca]]);
4134            }
4135            tris = next;
4136        }
4137        let flat: Vec<f32> = pos
4138            .iter()
4139            .flat_map(|p| {
4140                let n = (p[0] * p[0] + p[1] * p[1] + p[2] * p[2]).sqrt();
4141                [p[0] / n, p[1] / n, p[2] / n].map(|c| c as f32)
4142            })
4143            .collect();
4144        let idx: Vec<u32> = tris.iter().flat_map(|t| *t).collect();
4145        Mesh::from_buffers(&flat, &idx).unwrap()
4146    }
4147
4148    /// A saddle `z = (2 x^2 - y^2) / s` over `[-1, 1]^2`, `n` cells a side,
4149    /// facing up (normals `+z`): mean curvature about `-1 / s` at the middle.
4150    fn saddle(n: u32, s: f32) -> Mesh {
4151        let mut pos = Vec::new();
4152        for y in 0..=n {
4153            for x in 0..=n {
4154                let (fx, fy) = (
4155                    x as f32 / n as f32 * 2.0 - 1.0,
4156                    y as f32 / n as f32 * 2.0 - 1.0,
4157                );
4158                pos.extend_from_slice(&[fx, fy, (2.0 * fx * fx - fy * fy) / s]);
4159            }
4160        }
4161        let mut idx = Vec::new();
4162        for y in 0..n {
4163            for x in 0..n {
4164                let a = y * (n + 1) + x;
4165                idx.extend_from_slice(&[a, a + 1, a + n + 2, a, a + n + 2, a + n + 1]);
4166            }
4167        }
4168        Mesh::from_buffers(&pos, &idx).unwrap()
4169    }
4170
4171    /// A thickness colour rule over `range`, clamping both ends.
4172    fn thick_rule(lo: f32, hi: f32) -> ColorRule {
4173        ColorRule {
4174            map: Colormap::Turbo,
4175            range: (lo, hi),
4176            below: OutOfRange::Clamp,
4177            above: OutOfRange::Clamp,
4178            center: None,
4179            base: [200, 200, 200, 255],
4180            nan: [128, 128, 128, 255],
4181        }
4182    }
4183
4184    /// A unit sheet (A) cut by a cube (B), 0.4 wide, centred on it.
4185    fn sheet_and_cube(a_mode: UpdateMode) -> (Scene, MeshId, MeshId) {
4186        let mut scene = Scene::new(SceneOptions::default());
4187        let a = scene.add_mesh(sheet(10, 0.1), with_mode(a_mode)).unwrap();
4188        let b = scene
4189            .add_mesh(cube([0.3, 0.3, -0.2], 0.4), MeshOptions::default())
4190            .unwrap();
4191        (scene, a, b)
4192    }
4193
4194    /// The field's values computed from scratch: every vertex of A, mapped
4195    /// to the world and into B's space with the scene's point accessors (not
4196    /// the production transform), with fresh sign data for B. B's own BVH is
4197    /// used, which must be current: the fast winding number depends on the
4198    /// tree's shape, while the closest distance does not.
4199    fn reference(scene: &mut Scene, id: FieldId) -> Vec<f32> {
4200        let f = scene.fields.iter().find(|f| f.id == id).unwrap();
4201        let (on, to, md, mode) = (f.desc.on, f.desc.to[0], f.desc.max_distance, f.desc.sign);
4202        let ma = scene.mesh(on).unwrap();
4203        let slots = ma.vertex_slots();
4204        let defined: Vec<bool> = (0..slots as VertexId)
4205            .map(|v| ma.is_vertex_alive(v) && ma.corner_count(v) > 0)
4206            .collect();
4207        let points: Vec<[f32; 3]> = (0..slots as VertexId)
4208            .filter(|&v| defined[v as usize])
4209            .map(|v| {
4210                let p = ma.position(v).unwrap().map(f64::from);
4211                let w = scene.local_to_world(on, p).unwrap();
4212                scene.world_to_local(to, w).unwrap().map(|c| c as f32)
4213            })
4214            .collect();
4215        let eb = scene.entries.get_mut(to).unwrap();
4216        assert!(eb.is_current(), "the reference needs B's BVH current");
4217        let sign = SignData::build(&mut eb.mesh, &eb.bvh, mode).unwrap();
4218        let mut out = vec![0.0; points.len()];
4219        let bound = crate::query::round_up(md);
4220        signed_distances(&eb.mesh, &eb.bvh, &sign, &points, bound, &mut out).unwrap();
4221        let mut values = out.into_iter().map(|v| {
4222            if f64::from(v.abs()) > md {
4223                f32::INFINITY
4224            } else {
4225                v
4226            }
4227        });
4228        defined
4229            .iter()
4230            .map(|&d| if d { values.next().unwrap() } else { f32::NAN })
4231            .collect()
4232    }
4233
4234    fn bits(v: &[f32]) -> Vec<u32> {
4235        v.iter().map(|x| x.to_bits()).collect()
4236    }
4237
4238    /// Surface tolerance for the reference, whose points take another
4239    /// rounding path (§11.9: relative to the scenes' unit size).
4240    const TOL: f32 = 1e-5;
4241
4242    /// Equal bits; or both finite within `TOL`; or one far (`+∞`) and the
4243    /// other within `TOL` of `max_distance`. Both infinite must be equal, so
4244    /// that `+∞` never matches `-∞`.
4245    fn close(g: f32, w: f32, md: f64) -> bool {
4246        if g.to_bits() == w.to_bits() {
4247            return true;
4248        }
4249        if g.is_nan() || w.is_nan() {
4250            return false;
4251        }
4252        match (g.is_finite(), w.is_finite()) {
4253            (true, true) => (g - w).abs() <= TOL,
4254            (false, false) => g == w,
4255            _ => {
4256                let (finite, infinite) = if g.is_finite() { (g, w) } else { (w, g) };
4257                infinite == f32::INFINITY && f64::from(finite.abs()) >= md - f64::from(TOL)
4258            }
4259        }
4260    }
4261
4262    fn assert_matches(scene: &mut Scene, id: FieldId) {
4263        let want = reference(scene, id);
4264        let md = scene
4265            .fields
4266            .iter()
4267            .find(|f| f.id == id)
4268            .unwrap()
4269            .desc
4270            .max_distance;
4271        let got = scene.field_values(id).unwrap().to_vec();
4272        assert_eq!(got.len(), want.len());
4273        for (v, (g, w)) in got.iter().zip(&want).enumerate() {
4274            assert!(close(*g, *w, md), "vertex {v}: got {g}, want {w}");
4275        }
4276        assert!(scene.field_status(id).unwrap().current);
4277    }
4278
4279    fn assert_colors(scene: &Scene, mesh: MeshId, rule: &ColorRule, id: FieldId) {
4280        let values = scene.field_values(id).unwrap();
4281        let colors = scene.colors(mesh).unwrap().unwrap();
4282        assert_eq!(colors.len(), values.len());
4283        for (v, (c, &x)) in colors.iter().zip(values).enumerate() {
4284            assert_eq!(*c, color_of(rule, x), "vertex {v}, value {x}");
4285        }
4286    }
4287
4288    fn contact_rule(d: f32) -> ColorRule {
4289        ColorRule {
4290            map: Colormap::Turbo,
4291            range: (-d, 0.0),
4292            below: OutOfRange::Clamp,
4293            above: OutOfRange::Base,
4294            center: None,
4295            base: [200, 200, 200, 255],
4296            nan: [255, 0, 255, 255],
4297        }
4298    }
4299
4300    fn distance_rule(d: f32, g: f32) -> ColorRule {
4301        ColorRule {
4302            map: Colormap::Coolwarm,
4303            range: (-d, g),
4304            below: OutOfRange::Clamp,
4305            above: OutOfRange::Base,
4306            center: Some(0.0),
4307            base: [200, 200, 200, 255],
4308            nan: [255, 0, 255, 255],
4309        }
4310    }
4311
4312    fn finite_count(scene: &Scene, id: FieldId) -> usize {
4313        scene
4314            .field_values(id)
4315            .unwrap()
4316            .iter()
4317            .filter(|v| v.is_finite())
4318            .count()
4319    }
4320
4321    fn colors_dirty(scene: &mut Scene, mesh: MeshId) -> Option<Vec<VertexId>> {
4322        scene
4323            .take_changes()
4324            .into_iter()
4325            .find(|c| c.id == mesh)
4326            .map_or(Some(Vec::new()), |c| c.colors_dirty)
4327    }
4328
4329    fn alive_vertex(scene: &Scene, m: MeshId, rng: &mut Lcg) -> Option<VertexId> {
4330        let mesh = scene.mesh(m).unwrap();
4331        let alive: Vec<VertexId> = (0..mesh.vertex_slots() as VertexId)
4332            .filter(|&v| mesh.is_vertex_alive(v))
4333            .collect();
4334        (!alive.is_empty()).then(|| alive[rng.below(alive.len())])
4335    }
4336
4337    fn alive_triangle(scene: &Scene, m: MeshId, rng: &mut Lcg) -> Option<u32> {
4338        let mesh = scene.mesh(m).unwrap();
4339        let alive: Vec<u32> = (0..mesh.triangle_slots() as u32)
4340            .filter(|&t| mesh.triangle(t).is_some())
4341            .collect();
4342        (!alive.is_empty()).then(|| alive[rng.below(alive.len())])
4343    }
4344
4345    fn random_mode(rng: &mut Lcg) -> UpdateMode {
4346        [UpdateMode::Eager, UpdateMode::Lazy, UpdateMode::Manual][rng.below(3)]
4347    }
4348
4349    fn random_sign(rng: &mut Lcg) -> SignMode {
4350        [SignMode::Pseudonormal, SignMode::Winding, SignMode::Auto][rng.below(3)]
4351    }
4352
4353    fn random_max_distance(rng: &mut Lcg) -> f64 {
4354        if rng.below(5) == 0 {
4355            f64::INFINITY
4356        } else {
4357            rng.range(0.03, 0.25)
4358        }
4359    }
4360
4361    /// One random operation of the walk on meshes `a` (sheet) and `b`
4362    /// (cube). Operations that do not apply (nothing to undo, no such
4363    /// vertex) are skipped.
4364    fn random_op(scene: &mut Scene, a: MeshId, b: MeshId, rng: &mut Lcg) {
4365        let m = if rng.below(2) == 0 { a } else { b };
4366        match rng.below(16) {
4367            0 | 1 => {
4368                let p = turned(
4369                    rng.range(-25.0, 25.0),
4370                    [
4371                        rng.range(-0.1, 0.1),
4372                        rng.range(-0.1, 0.1),
4373                        rng.range(-0.1, 0.1),
4374                    ],
4375                );
4376                scene.set_placement(m, p).unwrap();
4377            }
4378            2 => {
4379                if let Some(v) = alive_vertex(scene, a, rng) {
4380                    let z = rng.range(-0.2, 0.2) as f32;
4381                    scene
4382                        .edit(a, |tx| {
4383                            let p = tx.mesh().position(v).unwrap();
4384                            tx.set_positions(&[v], &[[p[0], p[1], z]])
4385                        })
4386                        .unwrap();
4387                }
4388            }
4389            3 => {
4390                if let Some(v) = alive_vertex(scene, b, rng) {
4391                    let s = rng.range(0.95, 1.05) as f32;
4392                    scene
4393                        .edit(b, |tx| {
4394                            let p = tx.mesh().position(v).unwrap();
4395                            tx.set_positions(&[v], &[p.map(|c| c * s)])
4396                        })
4397                        .unwrap();
4398                }
4399            }
4400            4 => {
4401                if let Some(t) = alive_triangle(scene, m, rng) {
4402                    scene.edit(m, |tx| tx.remove_triangles(&[t])).unwrap();
4403                }
4404            }
4405            5 => {
4406                // A new vertex on a new triangle with two alive vertices.
4407                let (Some(p), Some(q)) = (alive_vertex(scene, a, rng), alive_vertex(scene, a, rng))
4408                else {
4409                    return;
4410                };
4411                if p == q {
4412                    return;
4413                }
4414                let x = [rng.range(0.0, 1.0) as f32, rng.range(0.0, 1.0) as f32, 0.02];
4415                scene
4416                    .edit(a, |tx| {
4417                        let v = tx.add_vertices(&[x])?;
4418                        tx.add_triangles(&[[v[0], p, q]])
4419                    })
4420                    .unwrap();
4421            }
4422            6 => {
4423                // Removes an alive vertex no triangle uses, if any.
4424                let mesh = scene.mesh(m).unwrap();
4425                let unused = (0..mesh.vertex_slots() as VertexId)
4426                    .find(|&v| mesh.is_vertex_alive(v) && mesh.corner_count(v) == 0);
4427                if let Some(v) = unused {
4428                    scene.edit(m, |tx| tx.remove_vertices(&[v])).unwrap();
4429                }
4430            }
4431            7 => {
4432                // Splits a triangle at its centroid (a topology edit that
4433                // keeps a closed mesh closed).
4434                if let Some(t) = alive_triangle(scene, m, rng) {
4435                    scene
4436                        .edit(m, |tx| {
4437                            let [i, j, k] = tx.mesh().triangle(t).unwrap();
4438                            let ps = [i, j, k].map(|v| tx.mesh().position(v).unwrap());
4439                            let c = std::array::from_fn(|d| (ps[0][d] + ps[1][d] + ps[2][d]) / 3.0);
4440                            let v = tx.add_vertices(&[c])?[0];
4441                            tx.remove_triangles(&[t])?;
4442                            tx.add_triangles(&[[i, j, v], [j, k, v], [k, i, v]])
4443                        })
4444                        .unwrap();
4445                }
4446            }
4447            8 | 9 => {
4448                scene.undo().unwrap();
4449            }
4450            10 => {
4451                scene.redo().unwrap();
4452            }
4453            11 => {
4454                // A drag in one step.
4455                scene.begin_step();
4456                for k in 1..=3 {
4457                    let d = 0.01 * k as f64;
4458                    scene
4459                        .set_placement(m, turned(d * 50.0, [d, -d, d]))
4460                        .unwrap();
4461                }
4462                scene.end_step().unwrap();
4463            }
4464            12 => {
4465                // A drag cancelled half-way.
4466                scene.begin_step();
4467                scene.set_placement(m, at(0.03, 0.02, -0.04)).unwrap();
4468                if let Some(v) = alive_vertex(scene, a, rng) {
4469                    scene
4470                        .edit(a, |tx| {
4471                            let p = tx.mesh().position(v).unwrap();
4472                            tx.set_positions(&[v], &[[p[0], p[1], 0.1]])
4473                        })
4474                        .unwrap();
4475                }
4476                scene.cancel_step().unwrap();
4477            }
4478            13 => {
4479                scene.compact(m).unwrap();
4480            }
4481            14 => {
4482                scene.set_mode(m, random_mode(rng)).unwrap();
4483            }
4484            _ => {
4485                // Add a vertex, then remove it in the same batch (or let undo
4486                // take back an add): exercises slot growth without a net
4487                // change.
4488                let x = [rng.range(0.0, 1.0) as f32, rng.range(0.0, 1.0) as f32, 0.0];
4489                let Some(p) = alive_vertex(scene, a, rng) else {
4490                    return;
4491                };
4492                let Some(q) = alive_vertex(scene, a, rng).filter(|&q| q != p) else {
4493                    return;
4494                };
4495                scene
4496                    .edit(a, |tx| {
4497                        let v = tx.add_vertices(&[x])?;
4498                        tx.add_triangles(&[[v[0], p, q]])
4499                    })
4500                    .unwrap();
4501                scene.undo().unwrap();
4502            }
4503        }
4504    }
4505
4506    /// `update_fields`, bringing stale Manual BVHs up to date when it asks.
4507    fn update_all(scene: &mut Scene) {
4508        for _ in 0..8 {
4509            match scene.update_fields() {
4510                Ok(()) => return,
4511                Err(SceneError::StaleBvh(id)) => scene.update(id).unwrap(),
4512                Err(e) => panic!("update_fields failed: {e}"),
4513            }
4514        }
4515        panic!("update_fields kept failing");
4516    }
4517
4518    fn check_colors_reported(
4519        scene: &Scene,
4520        mesh: MeshId,
4521        before: &[[u8; 4]],
4522        dirty: Option<&Vec<VertexId>>,
4523        at: &str,
4524    ) {
4525        let now = scene.colors(mesh).unwrap().unwrap();
4526        assert_eq!(now.len(), scene.mesh(mesh).unwrap().vertex_slots(), "{at}");
4527        if let Some(dirty) = dirty {
4528            for (v, c) in now.iter().enumerate() {
4529                if before.get(v) != Some(c) {
4530                    assert!(
4531                        dirty.binary_search(&(v as VertexId)).is_ok(),
4532                        "{at}: vertex {v} not reported"
4533                    );
4534                }
4535            }
4536        }
4537    }
4538
4539    #[test]
4540    fn field_matches_from_scratch() {
4541        let mut rng = Lcg(42);
4542        for round in 0..24 {
4543            let mut scene = Scene::new(SceneOptions::default());
4544            let a_mode = [UpdateMode::Eager, UpdateMode::Lazy, UpdateMode::Manual][round % 3];
4545            let a = scene.add_mesh(sheet(10, 0.1), with_mode(a_mode)).unwrap();
4546            let b = scene
4547                .add_mesh(
4548                    cube([0.3, 0.3, -0.2], 0.4),
4549                    with_mode(random_mode(&mut rng)),
4550                )
4551                .unwrap();
4552            scene.update(b).unwrap();
4553            let fa = scene
4554                .add_distance_field(DistanceField {
4555                    sign: random_sign(&mut rng),
4556                    ..field("d", a, b, random_max_distance(&mut rng))
4557                })
4558                .unwrap();
4559            let fb = scene
4560                .add_distance_field(DistanceField {
4561                    sign: random_sign(&mut rng),
4562                    ..field("d", b, a, random_max_distance(&mut rng))
4563                })
4564                .unwrap();
4565            let (rule_a, rule_b) = (distance_rule(0.2, 0.1), contact_rule(0.1));
4566            for (m, f, rule) in [(a, fa, &rule_a), (b, fb, &rule_b)] {
4567                let rule = rule.clone();
4568                scene
4569                    .set_coloring(m, Some(Coloring { field: f, rule }))
4570                    .unwrap();
4571            }
4572            update_all(&mut scene);
4573            scene.take_changes();
4574            for step in 0..40 {
4575                let at = format!("round {round} step {step}");
4576                let before_a = scene.colors(a).unwrap().unwrap().to_vec();
4577                let before_b = scene.colors(b).unwrap().unwrap().to_vec();
4578                let values_a = bits_of(scene.color_values(a).unwrap().unwrap());
4579                let values_b = bits_of(scene.color_values(b).unwrap().unwrap());
4580                // Eager meshes see one operation at a time; others a batch.
4581                let ops = if a_mode == UpdateMode::Eager {
4582                    1
4583                } else {
4584                    1 + rng.below(5)
4585                };
4586                for _ in 0..ops {
4587                    random_op(&mut scene, a, b, &mut rng);
4588                    // The colour buffers always cover every vertex slot.
4589                    for m in [a, b] {
4590                        let n = scene.mesh(m).unwrap().vertex_slots();
4591                        assert_eq!(scene.colors(m).unwrap().unwrap().len(), n, "{at}");
4592                    }
4593                }
4594                update_all(&mut scene);
4595                for m in [a, b] {
4596                    // The reference reads B's BVH, which update_fields made
4597                    // current only if a field needed it.
4598                    scene.update(m).unwrap();
4599                }
4600                assert_matches(&mut scene, fa);
4601                assert_matches(&mut scene, fb);
4602                assert_colors(&scene, a, &rule_a, fa);
4603                assert_colors(&scene, b, &rule_b, fb);
4604                let ch = scene.take_changes();
4605                let dirty = |m: MeshId| {
4606                    ch.iter()
4607                        .find(|c| c.id == m)
4608                        .map_or(Some(Vec::new()), |c| c.colors_dirty.clone())
4609                };
4610                check_colors_reported(&scene, a, &before_a, dirty(a).as_ref(), &at);
4611                check_colors_reported(&scene, b, &before_b, dirty(b).as_ref(), &at);
4612                for (m, f, before) in [(a, fa, &values_a), (b, fb, &values_b)] {
4613                    let now = scene.color_values(m).unwrap().unwrap();
4614                    assert_eq!(
4615                        bits_of(now),
4616                        bits_of(scene.field_values(f).unwrap()),
4617                        "{at}"
4618                    );
4619                    check_values_reported(before, now, dirty(m).as_ref(), &at);
4620                }
4621            }
4622        }
4623    }
4624
4625    /// An add undone before a Lazy field updates leaves nothing for the
4626    /// field to do, but the colours must still cover the new slot.
4627    #[test]
4628    fn colors_cover_slots_after_cancelled_add() {
4629        let (mut scene, a, b) = sheet_and_cube(UpdateMode::Lazy);
4630        let f = scene.add_distance_field(field("d", a, b, 0.1)).unwrap();
4631        let rule = contact_rule(0.1);
4632        let coloring = Coloring {
4633            field: f,
4634            rule: rule.clone(),
4635        };
4636        scene.set_coloring(a, Some(coloring)).unwrap();
4637        scene.update_fields().unwrap();
4638        scene.take_changes();
4639        scene
4640            .edit(a, |tx| {
4641                let v = tx.add_vertices(&[[0.5, 0.5, 0.0]])?;
4642                tx.add_triangles(&[[v[0], 0, 1]])
4643            })
4644            .unwrap();
4645        assert_eq!(scene.colors(a).unwrap().unwrap().len(), 122);
4646        assert_eq!(scene.colors(a).unwrap().unwrap()[121], rule.nan);
4647        scene.undo().unwrap();
4648        scene.update_fields().unwrap();
4649        assert!(scene.field_status(f).unwrap().current);
4650        assert_eq!(scene.colors(a).unwrap().unwrap().len(), 122);
4651        assert_colors(&scene, a, &rule, f);
4652        assert_eq!(colors_dirty(&mut scene, a), Some(vec![121]));
4653    }
4654
4655    fn bits_of(values: &[f32]) -> Vec<u32> {
4656        values.iter().map(|x| x.to_bits()).collect()
4657    }
4658
4659    /// The vertices whose value bits differ between `before` and `now`
4660    /// (slots beyond `before` included) are all in `dirty` (`None`: all).
4661    fn check_values_reported(before: &[u32], now: &[f32], dirty: Option<&Vec<VertexId>>, at: &str) {
4662        if let Some(dirty) = dirty {
4663            for (v, x) in now.iter().enumerate() {
4664                if before.get(v) != Some(&x.to_bits()) {
4665                    assert!(
4666                        dirty.binary_search(&(v as VertexId)).is_ok(),
4667                        "{at}: value of vertex {v} not reported"
4668                    );
4669                }
4670            }
4671        }
4672    }
4673
4674    /// The values behind the colours are the colouring field's channel, and
4675    /// change only where the colours are reported to change.
4676    #[test]
4677    fn color_values_view_the_coloring_field() {
4678        let (mut scene, a, b) = sheet_and_cube(UpdateMode::Eager);
4679        let f = scene.add_distance_field(field("d", a, b, 0.1)).unwrap();
4680        assert_eq!(scene.color_values(a), Ok(None));
4681        let rule = contact_rule(0.1);
4682        scene
4683            .set_coloring(
4684                a,
4685                Some(Coloring {
4686                    field: f,
4687                    rule: rule.clone(),
4688                }),
4689            )
4690            .unwrap();
4691        let view = |s: &Scene| bits_of(s.color_values(a).unwrap().unwrap());
4692        assert_eq!(view(&scene), bits_of(scene.field_values(f).unwrap()));
4693        assert_eq!(colors_dirty(&mut scene, a), None);
4694        // A drag: the values that changed are reported with the colours.
4695        let before = view(&scene);
4696        scene.set_placement(b, at(0.0, 0.0, 0.03)).unwrap();
4697        assert_eq!(view(&scene), bits_of(scene.field_values(f).unwrap()));
4698        assert_ne!(view(&scene), before);
4699        let dirty = colors_dirty(&mut scene, a);
4700        check_values_reported(
4701            &before,
4702            scene.color_values(a).unwrap().unwrap(),
4703            dirty.as_ref(),
4704            "drag",
4705        );
4706        // Colouring off: no values.
4707        scene.set_coloring(a, None).unwrap();
4708        assert_eq!(scene.color_values(a), Ok(None));
4709
4710        // A reused slot: the store resets its value to NaN, and the colours
4711        // report it. A Lazy field leaves a value in the slot until it updates.
4712        let (mut scene, a, b) = sheet_and_cube(UpdateMode::Lazy);
4713        let f = scene.add_distance_field(field("d", a, b, 0.1)).unwrap();
4714        scene
4715            .set_coloring(
4716                a,
4717                Some(Coloring {
4718                    field: f,
4719                    rule: rule.clone(),
4720                }),
4721            )
4722            .unwrap();
4723        scene
4724            .edit(a, |tx| {
4725                let v = tx.add_vertices(&[[0.5, 0.5, 0.0]])?;
4726                tx.add_triangles(&[[v[0], 0, 1]])
4727            })
4728            .unwrap();
4729        scene.update_fields().unwrap();
4730        scene.undo().unwrap();
4731        // An edit drops the redo step, which releases the slot.
4732        scene
4733            .edit(a, |tx| {
4734                let p = tx.mesh().position(0).unwrap();
4735                tx.set_positions(&[0], &[p])
4736            })
4737            .unwrap();
4738        scene.take_changes();
4739        let before = view(&scene);
4740        assert!(!f32::from_bits(before[121]).is_nan());
4741        scene
4742            .edit(a, |tx| tx.add_vertices(&[[5.0, 5.0, 5.0]]))
4743            .unwrap();
4744        assert!(
4745            scene.mesh(a).unwrap().is_vertex_alive(121),
4746            "the released slot is reused"
4747        );
4748        assert_eq!(scene.mesh(a).unwrap().vertex_slots(), 122);
4749        assert_eq!(view(&scene), bits_of(scene.field_values(f).unwrap()));
4750        let dirty = colors_dirty(&mut scene, a);
4751        check_values_reported(
4752            &before,
4753            scene.color_values(a).unwrap().unwrap(),
4754            dirty.as_ref(),
4755            "reuse",
4756        );
4757        // The colour of the reused slot follows its value (NaN) too.
4758        assert_eq!(scene.colors(a).unwrap().unwrap()[121], rule.nan);
4759    }
4760
4761    /// The same for a field measured to several targets: the values behind
4762    /// the colours are its channel, a move of either target reports the
4763    /// values it changes, and a reused slot's value (NaN) and colour are
4764    /// reported, with no nearest target.
4765    #[test]
4766    fn color_values_view_a_multi_target_field() {
4767        let rule = contact_rule(0.1);
4768        let colour = |scene: &mut Scene, a: MeshId, f: FieldId| {
4769            scene
4770                .set_coloring(
4771                    a,
4772                    Some(Coloring {
4773                        field: f,
4774                        rule: rule.clone(),
4775                    }),
4776                )
4777                .unwrap();
4778        };
4779        let view = |s: &Scene, a: MeshId| bits_of(s.color_values(a).unwrap().unwrap());
4780
4781        let (mut scene, a, b) = sheet_and_cube(UpdateMode::Eager);
4782        let c = scene
4783            .add_mesh(cube([0.7, 0.7, -0.15], 0.2), MeshOptions::default())
4784            .unwrap();
4785        let f = scene
4786            .add_distance_field(DistanceField {
4787                to: vec![b, c],
4788                ..field("d", a, b, 0.1)
4789            })
4790            .unwrap();
4791        colour(&mut scene, a, f);
4792        assert_eq!(view(&scene, a), bits_of(scene.field_values(f).unwrap()));
4793        let nearest = scene.field_nearest(f).unwrap();
4794        assert!(
4795            nearest.contains(&0) && nearest.contains(&1),
4796            "both targets win vertices"
4797        );
4798        assert_eq!(colors_dirty(&mut scene, a), None);
4799        assert_colors(&scene, a, &rule, f);
4800        // Moving either target: the values that changed are reported with the colours.
4801        for (m, p) in [(b, at(0.0, 0.0, 0.03)), (c, at(-0.02, 0.0, 0.02))] {
4802            let before = view(&scene, a);
4803            scene.set_placement(m, p).unwrap();
4804            assert_eq!(view(&scene, a), bits_of(scene.field_values(f).unwrap()));
4805            assert_ne!(view(&scene, a), before);
4806            let dirty = colors_dirty(&mut scene, a);
4807            check_values_reported(
4808                &before,
4809                scene.color_values(a).unwrap().unwrap(),
4810                dirty.as_ref(),
4811                "move",
4812            );
4813            assert_colors(&scene, a, &rule, f);
4814        }
4815        assert_eq!(shown(&scene, f), min_of_singles(&mut scene, f));
4816
4817        // A reused slot, as for one target (see above).
4818        let (mut scene, a, b) = sheet_and_cube(UpdateMode::Lazy);
4819        let c = scene
4820            .add_mesh(cube([0.7, 0.7, -0.15], 0.2), MeshOptions::default())
4821            .unwrap();
4822        let f = scene
4823            .add_distance_field(DistanceField {
4824                to: vec![b, c],
4825                ..field("d", a, b, 0.1)
4826            })
4827            .unwrap();
4828        colour(&mut scene, a, f);
4829        scene
4830            .edit(a, |tx| {
4831                let v = tx.add_vertices(&[[0.5, 0.5, 0.0]])?;
4832                tx.add_triangles(&[[v[0], 0, 1]])
4833            })
4834            .unwrap();
4835        scene.update_fields().unwrap();
4836        scene.undo().unwrap();
4837        scene
4838            .edit(a, |tx| {
4839                let p = tx.mesh().position(0).unwrap();
4840                tx.set_positions(&[0], &[p])
4841            })
4842            .unwrap();
4843        scene.take_changes();
4844        let before = view(&scene, a);
4845        assert!(!f32::from_bits(before[121]).is_nan());
4846        scene
4847            .edit(a, |tx| tx.add_vertices(&[[5.0, 5.0, 5.0]]))
4848            .unwrap();
4849        assert!(
4850            scene.mesh(a).unwrap().is_vertex_alive(121),
4851            "the released slot is reused"
4852        );
4853        assert_eq!(view(&scene, a), bits_of(scene.field_values(f).unwrap()));
4854        let dirty = colors_dirty(&mut scene, a);
4855        check_values_reported(
4856            &before,
4857            scene.color_values(a).unwrap().unwrap(),
4858            dirty.as_ref(),
4859            "reuse",
4860        );
4861        assert_eq!(scene.colors(a).unwrap().unwrap()[121], rule.nan);
4862        assert_eq!(shown(&scene, f).1[121], u32::MAX);
4863    }
4864
4865    #[test]
4866    fn small_drag_recomputes_only_near_vertices() {
4867        let mut scene = Scene::new(SceneOptions::default());
4868        // 61 x 61 = 3721 vertices.
4869        let a = scene
4870            .add_mesh(sheet(60, 1.0 / 60.0), MeshOptions::default())
4871            .unwrap();
4872        let b = scene
4873            .add_mesh(cube([0.1, 0.1, -0.05], 0.1), MeshOptions::default())
4874            .unwrap();
4875        let f = scene.add_distance_field(field("d", a, b, 0.02)).unwrap();
4876        scene.update_fields().unwrap();
4877        assert_matches(&mut scene, f);
4878        assert!(finite_count(&scene, f) > 0);
4879        counter::take();
4880        scene.set_placement(b, at(0.002, 0.001, 0.0)).unwrap();
4881        scene.update_fields().unwrap();
4882        let recomputed = counter::take();
4883        assert!(recomputed > 0);
4884        assert!(recomputed < 300, "recomputed {recomputed} of 3721");
4885        assert_matches(&mut scene, f);
4886        // An infinite max_distance computes every vertex.
4887        let g = scene
4888            .add_distance_field(field("all", a, b, f64::INFINITY))
4889            .unwrap();
4890        scene.update_fields().unwrap();
4891        counter::take();
4892        scene.set_placement(b, at(0.003, 0.001, 0.0)).unwrap();
4893        scene.update_fields().unwrap();
4894        assert!(counter::take() >= 3721);
4895        assert_matches(&mut scene, g);
4896        assert!(scene.field_values(g).unwrap().iter().all(|v| v.is_finite()));
4897    }
4898
4899    #[test]
4900    fn reciprocal_fields_share_one_pair_query() {
4901        // Two-sided colouring with the same max_distance: one shared pair
4902        // query per pose, same values as separate queries.
4903        let mut scene = Scene::new(SceneOptions::default());
4904        let a = scene
4905            .add_mesh(sheet(20, 0.05), MeshOptions::default())
4906            .unwrap();
4907        let b = scene
4908            .add_mesh(cube([0.2, 0.2, -0.05], 0.2), MeshOptions::default())
4909            .unwrap();
4910        let fa = scene.add_distance_field(field("d", a, b, 0.1)).unwrap();
4911        let fb = scene.add_distance_field(field("d", b, a, 0.1)).unwrap();
4912        scene.update_fields().unwrap();
4913        scene.take_changes();
4914        counter::take_shared();
4915        scene.set_placement(b, at(0.01, 0.005, 0.0)).unwrap();
4916        scene.update_fields().unwrap();
4917        assert_eq!(counter::take_shared(), 1);
4918        assert_matches(&mut scene, fa);
4919        assert_matches(&mut scene, fb);
4920    }
4921
4922    /// Six sheets of `n` x `n` cells in `mode`, stacked `gap` apart, with a
4923    /// field for every ordered pair (30, all within reach), each mesh
4924    /// coloured by its field to the next mesh by index.
4925    fn six_sheets(n: u32, gap: f64, mode: UpdateMode) -> (Scene, Vec<MeshId>) {
4926        let mut scene = Scene::new(SceneOptions::default());
4927        let ids: Vec<MeshId> = (0..6)
4928            .map(|k| {
4929                let opts = MeshOptions {
4930                    mode,
4931                    placement: at(0.0, 0.0, k as f64 * gap),
4932                    ..MeshOptions::default()
4933                };
4934                scene.add_mesh(sheet(n, 1.0 / n as f32), opts).unwrap()
4935            })
4936            .collect();
4937        for (i, &a) in ids.iter().enumerate() {
4938            for (j, &b) in ids.iter().enumerate() {
4939                if i != j {
4940                    let id = scene
4941                        .add_distance_field(field(&format!("to{j}"), a, b, 8.0 * gap))
4942                        .unwrap();
4943                    if j == (i + 1) % 6 {
4944                        let rule = distance_rule(8.0 * gap as f32, 8.0 * gap as f32);
4945                        let coloring = Coloring { field: id, rule };
4946                        scene.set_coloring(a, Some(coloring)).unwrap();
4947                    }
4948                }
4949            }
4950        }
4951        scene.update_fields().unwrap();
4952        (scene, ids)
4953    }
4954
4955    /// Per pose of a drag of the first mesh: the reports, every field's
4956    /// values and every mesh's colours.
4957    type Drag = Vec<(Vec<crate::MeshChanges>, Vec<Vec<u32>>, Vec<Vec<[u8; 4]>>)>;
4958
4959    fn drag(n: u32, mode: UpdateMode) -> Drag {
4960        let gap = 0.01;
4961        let (mut scene, ids) = six_sheets(n, gap, mode);
4962        scene.take_changes();
4963        let poses = [
4964            turned(0.5, [0.003, 0.001, 0.002]),
4965            turned(-1.0, [0.01, -0.004, 0.025]),
4966            turned(2.0, [0.0, 0.002, 0.051]),
4967            at(0.0, 0.0, 0.0),
4968        ];
4969        let mut out = Vec::new();
4970        for p in poses {
4971            scene.set_placement(ids[0], p).unwrap();
4972            scene.update_fields().unwrap();
4973            let changes = scene.take_changes();
4974            let ids_f: Vec<FieldId> = scene.fields.iter().map(|f| f.id).collect();
4975            let values = ids_f
4976                .iter()
4977                .map(|&f| bits(scene.field_values(f).unwrap()))
4978                .collect();
4979            let colors = ids
4980                .iter()
4981                .map(|&m| scene.colors(m).unwrap().unwrap().to_vec())
4982                .collect();
4983            out.push((changes, values, colors));
4984        }
4985        out
4986    }
4987
4988    #[test]
4989    fn parallel_field_updates_report_in_order() {
4990        use fast_mesh_store::par;
4991        // The forced-sequential run is the code a feature-off build runs.
4992        for mode in [UpdateMode::Eager, UpdateMode::Lazy] {
4993            par::set_force_sequential(true);
4994            let reference = drag(24, mode);
4995            par::set_force_sequential(false);
4996            assert!(
4997                reference.iter().all(|(c, _, _)| c.len() == 6),
4998                "{mode:?}: every mesh reports"
4999            );
5000            if !par::enabled() {
5001                // Every drag below would be this one again.
5002                continue;
5003            }
5004            for k in [1, 2, 8] {
5005                let got = par::with_threads(k, || drag(24, mode));
5006                assert!(got == reference, "{mode:?}: {k} threads");
5007            }
5008            counter::take_parallel();
5009            assert!(drag(24, mode) == reference, "{mode:?}: global pool");
5010            assert!(counter::take_parallel() > 0, "{mode:?}: ran sequentially");
5011        }
5012    }
5013
5014    #[test]
5015    fn small_inputs_stay_sequential() {
5016        use fast_mesh_store::par;
5017        let before = par::parallel_entries();
5018        counter::take_parallel();
5019        let (mut scene, a, b) = sheet_and_cube(UpdateMode::Eager);
5020        let f = scene.add_distance_field(field("d", a, b, 0.1)).unwrap();
5021        scene.add_distance_field(field("d", b, a, 0.1)).unwrap();
5022        for k in 0..4 {
5023            scene
5024                .set_placement(b, at(0.01 * k as f64, 0.0, 0.0))
5025                .unwrap();
5026        }
5027        scene.update_fields().unwrap();
5028        assert_matches(&mut scene, f);
5029        assert_eq!(par::parallel_entries(), before);
5030        assert_eq!(counter::take_parallel(), 0);
5031    }
5032
5033    #[test]
5034    fn near_work_counts_only_meshes_in_reach() {
5035        let (mut scene, a, b) = sheet_and_cube(UpdateMode::Lazy);
5036        scene.add_distance_field(field("d", a, b, 0.1)).unwrap();
5037        scene.update_fields().unwrap();
5038        let tris = scene.mesh(a).unwrap().triangle_count();
5039        // The sheet has fewer vertices than triangles, so only the near
5040        // search can bring the estimate up to its triangle count.
5041        assert!(scene.mesh(a).unwrap().vertex_count() < tris);
5042        for (x, in_reach) in [(0.01, true), (100.0, false)] {
5043            scene.set_placement(b, at(x, 0.0, 0.0)).unwrap();
5044            let job = scene.prepare_field(0).unwrap();
5045            assert!(job.near_margins[0].is_some());
5046            assert_eq!(job.work >= tris, in_reach, "x = {x}");
5047        }
5048    }
5049
5050    #[test]
5051    fn leaving_contact_restores_far() {
5052        let (mut scene, a, b) = sheet_and_cube(UpdateMode::Eager);
5053        let f = scene.add_distance_field(field("d", a, b, 0.1)).unwrap();
5054        assert_matches(&mut scene, f);
5055        let touching = scene.field_values(f).unwrap().to_vec();
5056        assert!(touching.iter().any(|v| *v < 0.0));
5057        assert!(touching.iter().any(|v| *v > 0.0 && v.is_finite()));
5058        scene.set_placement(b, at(10.0, 0.0, 0.0)).unwrap();
5059        assert_matches(&mut scene, f);
5060        assert!(
5061            scene
5062                .field_values(f)
5063                .unwrap()
5064                .iter()
5065                .all(|v| *v == f32::INFINITY)
5066        );
5067        scene.set_placement(b, Placement::IDENTITY).unwrap();
5068        assert_matches(&mut scene, f);
5069        assert_eq!(bits(scene.field_values(f).unwrap()), bits(&touching));
5070    }
5071
5072    #[test]
5073    fn undo_keeps_field_consistent() {
5074        let (mut scene, a, b) = sheet_and_cube(UpdateMode::Eager);
5075        let f = scene.add_distance_field(field("d", a, b, 0.15)).unwrap();
5076        let g = scene.add_distance_field(field("d", b, a, 0.15)).unwrap();
5077        let initial = bits(scene.field_values(f).unwrap());
5078        // Removing a triangle unreferences vertex 0 (only in triangle 0).
5079        scene.edit(a, |tx| tx.remove_triangles(&[0])).unwrap();
5080        assert!(scene.field_values(f).unwrap()[0].is_nan());
5081        scene.edit(a, |tx| tx.remove_vertices(&[0])).unwrap();
5082        scene
5083            .edit(a, |tx| tx.set_positions(&[45], &[[0.5, 0.4, 0.3]]))
5084            .unwrap();
5085        scene
5086            .edit(a, |tx| {
5087                let v = tx.add_vertices(&[[0.45, 0.45, -0.05]])?;
5088                tx.add_triangles(&[[v[0], 44, 45]])
5089            })
5090            .unwrap();
5091        scene.set_placement(b, at(0.05, 0.0, 0.02)).unwrap();
5092        for _ in 0..5 {
5093            assert_matches(&mut scene, f);
5094            assert_matches(&mut scene, g);
5095            scene.undo().unwrap();
5096        }
5097        assert_matches(&mut scene, f);
5098        assert_matches(&mut scene, g);
5099        // The undone vertex leaves a dead slot behind, which holds NaN.
5100        let back = scene.field_values(f).unwrap();
5101        assert_eq!(bits(&back[..initial.len()]), initial);
5102        assert!(back[initial.len()..].iter().all(|v| v.is_nan()));
5103        for _ in 0..5 {
5104            scene.redo().unwrap();
5105            assert_matches(&mut scene, f);
5106            assert_matches(&mut scene, g);
5107        }
5108        // Cancelling an open step restores the values too.
5109        scene.begin_step();
5110        scene.set_placement(b, at(0.0, 0.0, 0.5)).unwrap();
5111        scene.cancel_step().unwrap();
5112        assert_matches(&mut scene, f);
5113    }
5114
5115    #[test]
5116    fn manual_bvh_blocks_field_update() {
5117        let mut scene = Scene::new(SceneOptions::default());
5118        let a = scene
5119            .add_mesh(sheet(10, 0.1), MeshOptions::default())
5120            .unwrap();
5121        let b = scene
5122            .add_mesh(cube([0.3, 0.3, -0.2], 0.4), with_mode(UpdateMode::Manual))
5123            .unwrap();
5124        let f = scene.add_distance_field(field("d", a, b, 0.1)).unwrap();
5125        assert_matches(&mut scene, f);
5126        scene
5127            .edit(b, |tx| tx.set_positions(&[7], &[[0.8, 0.8, 0.3]]))
5128            .unwrap();
5129        // The Eager field could not update: it is left stale.
5130        assert!(!scene.field_status(f).unwrap().current);
5131        assert_eq!(scene.update_fields(), Err(SceneError::StaleBvh(b)));
5132        assert_eq!(scene.flush(), Err(SceneError::StaleBvh(b)));
5133        assert!(!scene.field_status(f).unwrap().current);
5134        scene.update(b).unwrap();
5135        scene.update_fields().unwrap();
5136        assert_matches(&mut scene, f);
5137        // Registering a field on a stale Manual target fails.
5138        scene
5139            .edit(b, |tx| tx.set_positions(&[7], &[[0.7, 0.7, 0.2]]))
5140            .unwrap();
5141        assert_eq!(
5142            scene.add_distance_field(field("e", a, b, 0.1)),
5143            Err(SceneError::StaleBvh(b))
5144        );
5145    }
5146
5147    #[test]
5148    fn eager_vs_lazy_field_scheduling() {
5149        for mode in [UpdateMode::Eager, UpdateMode::Lazy, UpdateMode::Manual] {
5150            let (mut scene, a, b) = sheet_and_cube(mode);
5151            let f = scene.add_distance_field(field("d", a, b, 0.1)).unwrap();
5152            assert_eq!(
5153                scene.field_status(f).unwrap().current,
5154                mode == UpdateMode::Eager
5155            );
5156            scene.update_fields().unwrap();
5157            scene.set_placement(b, at(0.05, 0.0, 0.0)).unwrap();
5158            assert_eq!(
5159                scene.field_status(f).unwrap().current,
5160                mode == UpdateMode::Eager
5161            );
5162            scene.flush().unwrap();
5163            assert_eq!(
5164                scene.field_status(f).unwrap().current,
5165                mode != UpdateMode::Manual
5166            );
5167            scene.update_fields().unwrap();
5168            assert_matches(&mut scene, f);
5169            // Edits of A schedule the same way.
5170            scene
5171                .edit(a, |tx| tx.set_positions(&[45], &[[0.5, 0.4, 0.05]]))
5172                .unwrap();
5173            assert_eq!(
5174                scene.field_status(f).unwrap().current,
5175                mode == UpdateMode::Eager
5176            );
5177            if mode == UpdateMode::Manual {
5178                scene.update(a).unwrap();
5179            }
5180            // An unrelated mesh leaves the field alone.
5181            let c = scene
5182                .add_mesh(sheet(1, 1.0), MeshOptions::default())
5183                .unwrap();
5184            scene.update_fields().unwrap();
5185            scene.set_placement(c, at(3.0, 0.0, 0.0)).unwrap();
5186            assert!(scene.field_status(f).unwrap().current);
5187            assert_matches(&mut scene, f);
5188        }
5189    }
5190
5191    #[test]
5192    fn colors_follow_values_and_report_dirty() {
5193        let (mut scene, a, b) = sheet_and_cube(UpdateMode::Eager);
5194        let f = scene.add_distance_field(field("d", a, b, 0.1)).unwrap();
5195        let s = scene.subscribe();
5196        scene.take_changes();
5197        assert_eq!(scene.colors(a), Ok(None));
5198        let rule = contact_rule(0.1);
5199        scene
5200            .set_coloring(
5201                a,
5202                Some(Coloring {
5203                    field: f,
5204                    rule: rule.clone(),
5205                }),
5206            )
5207            .unwrap();
5208        assert_colors(&scene, a, &rule, f);
5209        assert_eq!(colors_dirty(&mut scene, a), None);
5210        let before = scene.field_values(f).unwrap().to_vec();
5211        scene.set_placement(b, at(0.0, 0.0, 0.03)).unwrap();
5212        assert_colors(&scene, a, &rule, f);
5213        let after = scene.field_values(f).unwrap().to_vec();
5214        let changed: Vec<VertexId> = (0..after.len() as VertexId)
5215            .filter(|&v| before[v as usize].to_bits() != after[v as usize].to_bits())
5216            .collect();
5217        assert!(!changed.is_empty());
5218        let ch = scene.take_changes();
5219        let mine = ch.iter().find(|c| c.id == a).unwrap();
5220        assert_eq!(mine.colors_dirty.as_deref(), Some(&changed[..]));
5221        assert_eq!(mine.fields_changed, [f]);
5222        let theirs = scene.take_changes_for(s).unwrap();
5223        let theirs = theirs.iter().find(|c| c.id == a).unwrap();
5224        assert_eq!(theirs.colors_dirty, None, "the colouring was set since");
5225        assert_eq!(theirs.fields_changed, [f]);
5226        // Nothing changed, nothing reported.
5227        scene.update_fields().unwrap();
5228        assert!(scene.take_changes().is_empty());
5229        // Changing the colouring recolours everything.
5230        let other = distance_rule(0.1, 0.05);
5231        scene
5232            .set_coloring(
5233                a,
5234                Some(Coloring {
5235                    field: f,
5236                    rule: other.clone(),
5237                }),
5238            )
5239            .unwrap();
5240        assert_colors(&scene, a, &other, f);
5241        assert_eq!(colors_dirty(&mut scene, a), None);
5242        // Bad colourings are rejected and change nothing.
5243        let mut bad = other.clone();
5244        bad.range = (1.0, -1.0);
5245        assert!(matches!(
5246            scene.set_coloring(
5247                a,
5248                Some(Coloring {
5249                    field: f,
5250                    rule: bad
5251                })
5252            ),
5253            Err(SceneError::InvalidInput(_))
5254        ));
5255        assert!(matches!(
5256            scene.set_coloring(
5257                b,
5258                Some(Coloring {
5259                    field: f,
5260                    rule: other.clone()
5261                })
5262            ),
5263            Err(SceneError::InvalidInput(_))
5264        ));
5265        assert_colors(&scene, a, &other, f);
5266        assert!(scene.take_changes().is_empty());
5267        scene.set_coloring(a, None).unwrap();
5268        assert_eq!(scene.colors(a), Ok(None));
5269        assert_eq!(colors_dirty(&mut scene, a), None);
5270    }
5271
5272    #[test]
5273    fn late_subscriber_gets_full_colour_report() {
5274        // A subscriber added after a colouring exists gets `colors_dirty:
5275        // None` for the coloured mesh, so it knows to read every colour.
5276        let (mut scene, a, b) = sheet_and_cube(UpdateMode::Eager);
5277        let f = scene.add_distance_field(field("d", a, b, 0.1)).unwrap();
5278        let rule = contact_rule(0.1);
5279        scene
5280            .set_coloring(
5281                a,
5282                Some(Coloring {
5283                    field: f,
5284                    rule: rule.clone(),
5285                }),
5286            )
5287            .unwrap();
5288        scene.take_changes();
5289        let s = scene.subscribe();
5290        let changes = scene.take_changes_for(s).unwrap();
5291        let mine = changes.iter().find(|c| c.id == a).unwrap();
5292        assert_eq!(mine.colors_dirty, None);
5293        // An uncoloured mesh still starts clean.
5294        assert!(changes.iter().find(|c| c.id == b).is_none());
5295    }
5296
5297    #[test]
5298    fn coloring_getter_and_field_list() {
5299        let (mut scene, a, b) = sheet_and_cube(UpdateMode::Eager);
5300        assert_eq!(scene.coloring(a).unwrap(), None);
5301        assert_eq!(scene.fields_on(a).unwrap(), vec![]);
5302        let f1 = scene.add_distance_field(field("d1", a, b, 0.1)).unwrap();
5303        let f2 = scene.add_distance_field(field("d2", a, b, 0.2)).unwrap();
5304        assert_eq!(scene.fields_on(a).unwrap(), vec![f1, f2]);
5305        assert_eq!(scene.fields_on(b).unwrap(), vec![]);
5306        let rule = contact_rule(0.1);
5307        let coloring = Coloring {
5308            field: f1,
5309            rule: rule.clone(),
5310        };
5311        scene.set_coloring(a, Some(coloring.clone())).unwrap();
5312        assert_eq!(scene.coloring(a).unwrap(), Some(&coloring));
5313        scene.set_coloring(a, None).unwrap();
5314        assert_eq!(scene.coloring(a).unwrap(), None);
5315        scene.remove_field(f1).unwrap();
5316        assert_eq!(scene.fields_on(a).unwrap(), vec![f2]);
5317    }
5318
5319    #[test]
5320    fn reconfigure_field_keeps_id_and_colouring() {
5321        let (mut scene, a, b) = sheet_and_cube(UpdateMode::Eager);
5322        let f = scene.add_distance_field(field("d", a, b, 0.1)).unwrap();
5323        let rule = contact_rule(0.1);
5324        scene
5325            .set_coloring(
5326                a,
5327                Some(Coloring {
5328                    field: f,
5329                    rule: rule.clone(),
5330                }),
5331            )
5332            .unwrap();
5333        scene.take_changes();
5334        let before = scene.field_values(f).unwrap().to_vec();
5335        // Widening max_distance keeps the id and the colouring, and brings
5336        // more vertices into range.
5337        scene.reconfigure_field(f, 0.5, SignMode::Auto).unwrap();
5338        assert_eq!(scene.fields_on(a).unwrap(), vec![f]);
5339        assert!(scene.coloring(a).unwrap().is_some());
5340        // Eager: recomputed at once.
5341        assert!(scene.field_status(f).unwrap().current);
5342        let after = scene.field_values(f).unwrap().to_vec();
5343        assert_ne!(before, after);
5344        let finite_before = before.iter().filter(|v| v.is_finite()).count();
5345        let finite_after = after.iter().filter(|v| v.is_finite()).count();
5346        assert!(finite_after >= finite_before);
5347        // Invalid inputs are rejected and change nothing.
5348        assert!(matches!(
5349            scene.reconfigure_field(f, f64::NAN, SignMode::Auto),
5350            Err(SceneError::InvalidInput(_))
5351        ));
5352        assert!(scene.field_status(f).unwrap().current);
5353        // Same values: a no-op.
5354        let values = scene.field_values(f).unwrap().to_vec();
5355        scene.reconfigure_field(f, 0.5, SignMode::Auto).unwrap();
5356        assert!(scene.field_status(f).unwrap().current);
5357        assert_eq!(scene.field_values(f).unwrap(), &values[..]);
5358    }
5359
5360    #[test]
5361    fn one_sided_and_two_sided_coloring() {
5362        let (mut scene, a, b) = sheet_and_cube(UpdateMode::Eager);
5363        let d = 0.1;
5364        let fa = scene.add_distance_field(field("d", a, b, 0.2)).unwrap();
5365        let rule = contact_rule(d as f32);
5366        scene
5367            .set_coloring(
5368                a,
5369                Some(Coloring {
5370                    field: fa,
5371                    rule: rule.clone(),
5372                }),
5373            )
5374            .unwrap();
5375        assert_eq!(scene.colors(b), Ok(None));
5376        assert_colors(&scene, a, &rule, fa);
5377        // Inside vertices take the colormap, outside ones the base colour.
5378        let values = scene.field_values(fa).unwrap().to_vec();
5379        let colors = scene.colors(a).unwrap().unwrap().to_vec();
5380        for (v, c) in values.iter().zip(&colors) {
5381            if *v > 0.0 {
5382                assert_eq!(*c, rule.base);
5383            }
5384        }
5385        assert!(values.iter().any(|v| *v < 0.0));
5386        // Two-sided: B gets its own field to A and its own colouring.
5387        let fb = scene.add_distance_field(field("d", b, a, 0.2)).unwrap();
5388        let other = distance_rule(0.1, 0.1);
5389        scene
5390            .set_coloring(
5391                b,
5392                Some(Coloring {
5393                    field: fb,
5394                    rule: other.clone(),
5395                }),
5396            )
5397            .unwrap();
5398        scene.set_placement(b, at(0.02, 0.0, 0.05)).unwrap();
5399        assert_colors(&scene, a, &rule, fa);
5400        assert_colors(&scene, b, &other, fb);
5401        assert_matches(&mut scene, fa);
5402        assert_matches(&mut scene, fb);
5403    }
5404
5405    #[test]
5406    fn compact_recomputes_everything() {
5407        let (mut scene, a, b) = sheet_and_cube(UpdateMode::Lazy);
5408        let f = scene.add_distance_field(field("d", a, b, 0.1)).unwrap();
5409        let rule = contact_rule(0.1);
5410        scene
5411            .set_coloring(
5412                a,
5413                Some(Coloring {
5414                    field: f,
5415                    rule: rule.clone(),
5416                }),
5417            )
5418            .unwrap();
5419        scene.edit(a, |tx| tx.remove_triangles(&[0])).unwrap();
5420        scene.edit(a, |tx| tx.remove_vertices(&[0])).unwrap();
5421        scene.update_fields().unwrap();
5422        scene.take_changes();
5423        scene.compact(a).unwrap();
5424        let ch = scene.take_changes();
5425        let mine = ch.iter().find(|c| c.id == a).unwrap();
5426        assert!(mine.changes.is_none());
5427        assert_eq!(mine.colors_dirty, None);
5428        // The colours follow the remapped values right away.
5429        assert_eq!(scene.mesh(a).unwrap().vertex_slots(), 120);
5430        assert_colors(&scene, a, &rule, f);
5431        assert!(!scene.field_status(f).unwrap().current);
5432        counter::take();
5433        scene.update_fields().unwrap();
5434        assert_eq!(counter::take(), 120);
5435        assert_matches(&mut scene, f);
5436        assert_colors(&scene, a, &rule, f);
5437        // Compacting B recomputes too.
5438        scene.compact(b).unwrap();
5439        scene.update_fields().unwrap();
5440        assert_eq!(counter::take(), 120);
5441        assert_matches(&mut scene, f);
5442    }
5443
5444    #[test]
5445    fn removing_a_mesh_removes_its_fields() {
5446        let (mut scene, a, b) = sheet_and_cube(UpdateMode::Eager);
5447        let fa = scene.add_distance_field(field("d", a, b, 0.1)).unwrap();
5448        let fb = scene.add_distance_field(field("d", b, a, 0.1)).unwrap();
5449        scene
5450            .set_coloring(
5451                a,
5452                Some(Coloring {
5453                    field: fa,
5454                    rule: contact_rule(0.1),
5455                }),
5456            )
5457            .unwrap();
5458        scene.take_changes();
5459        scene.remove_mesh(b).unwrap();
5460        assert_eq!(scene.field_values(fa), Err(SceneError::UnknownField));
5461        assert_eq!(scene.field_values(fb), Err(SceneError::UnknownField));
5462        assert_eq!(scene.field_status(fa), Err(SceneError::UnknownField));
5463        assert!(scene.mesh(a).unwrap().channel("field:d").is_none());
5464        assert_eq!(scene.colors(a), Ok(None));
5465        let ch = scene.take_changes();
5466        let mine = ch.iter().find(|c| c.id == a).unwrap();
5467        assert_eq!(mine.fields_changed, [fa]);
5468        assert_eq!(mine.colors_dirty, None);
5469        assert_eq!(scene.remove_field(fa), Err(SceneError::UnknownField));
5470        // A field removed by hand goes the same way; the name is free again.
5471        let c = scene
5472            .add_mesh(cube([0.3, 0.3, -0.2], 0.4), MeshOptions::default())
5473            .unwrap();
5474        let g = scene.add_distance_field(field("d", a, c, 0.1)).unwrap();
5475        assert_eq!(scene.field_id(g.raw()), Some(g));
5476        assert_eq!(scene.field_id(fa.raw()), None);
5477        scene.remove_field(g).unwrap();
5478        assert_eq!(scene.field_id(g.raw()), None);
5479        assert_eq!(scene.field_values(g), Err(SceneError::UnknownField));
5480        assert!(scene.mesh(a).unwrap().channel("field:d").is_none());
5481        scene.add_distance_field(field("d", a, c, 0.1)).unwrap();
5482        // Editing goes on without the removed fields.
5483        scene
5484            .edit(a, |tx| tx.set_positions(&[45], &[[0.5, 0.4, 0.05]]))
5485            .unwrap();
5486        scene.update_fields().unwrap();
5487    }
5488
5489    /// Everything a renderer sees of two meshes after an update: every
5490    /// field's value bits, both meshes' colours, and the drained reports.
5491    type Seen = (
5492        Vec<Vec<u32>>,
5493        Vec<Option<Vec<[u8; 4]>>>,
5494        Vec<crate::MeshChanges>,
5495    );
5496
5497    fn seen(scene: &mut Scene, meshes: [MeshId; 2]) -> Seen {
5498        let ids: Vec<FieldId> = scene.fields.iter().map(|f| f.id).collect();
5499        let values = ids
5500            .iter()
5501            .map(|&f| bits(scene.field_values(f).unwrap()))
5502            .collect();
5503        let colors = meshes
5504            .iter()
5505            .map(|&m| scene.colors(m).unwrap().map(<[_]>::to_vec))
5506            .collect();
5507        (values, colors, scene.take_changes())
5508    }
5509
5510    /// A sheet (A) and a cube (B) with a field each way, both coloured,
5511    /// with random modes, signs and `max_distance`s (infinite ones too).
5512    fn twin_scene(rng: &mut Lcg) -> (Scene, MeshId, MeshId) {
5513        let mut scene = Scene::new(SceneOptions::default());
5514        let a = scene
5515            .add_mesh(sheet(10, 0.1), with_mode(random_mode(rng)))
5516            .unwrap();
5517        let b = scene
5518            .add_mesh(cube([0.3, 0.3, -0.2], 0.4), with_mode(random_mode(rng)))
5519            .unwrap();
5520        for (on, to, rule) in [(a, b, distance_rule(0.2, 0.1)), (b, a, contact_rule(0.1))] {
5521            let id = scene
5522                .add_distance_field(DistanceField {
5523                    sign: random_sign(rng),
5524                    ..field("d", on, to, random_max_distance(rng))
5525                })
5526                .unwrap();
5527            scene
5528                .set_coloring(on, Some(Coloring { field: id, rule }))
5529                .unwrap();
5530        }
5531        (scene, a, b)
5532    }
5533
5534    /// Runs the same random walk on two copies of a random scene, the first
5535    /// with the near-pair limit `on`, the second with `off`, and asserts
5536    /// after every update that both show the same values, colours and
5537    /// reports, bit for bit. Returns, per round, the near searches each copy
5538    /// abandoned, and whether the round has a field with a finite
5539    /// `max_distance` (only those search).
5540    fn assert_twins_agree(
5541        seed: u64,
5542        rounds: usize,
5543        on: usize,
5544        off: usize,
5545    ) -> Vec<([usize; 2], bool)> {
5546        let mut rng = Lcg(seed);
5547        let mut stats = Vec::new();
5548        for round in 0..rounds {
5549            let state = rng.0;
5550            let mut aborted = [0; 2];
5551            let mut twins = [(on, Lcg(state)), (off, Lcg(state))].map(|(limit, mut r)| {
5552                counter::set_near_limit(Some(limit));
5553                let (scene, a, b) = twin_scene(&mut r);
5554                (limit, r, scene, a, b)
5555            });
5556            rng.0 = twins[0].1.0;
5557            let finite = twins[0]
5558                .2
5559                .fields
5560                .iter()
5561                .any(|f| f.desc.max_distance.is_finite());
5562            counter::take_aborted();
5563            for step in 0..30 {
5564                let ops = 1 + rng.below(4);
5565                let mut out = Vec::new();
5566                for (k, (limit, r, scene, a, b)) in twins.iter_mut().enumerate() {
5567                    counter::set_near_limit(Some(*limit));
5568                    for _ in 0..ops {
5569                        random_op(scene, *a, *b, r);
5570                    }
5571                    update_all(scene);
5572                    out.push(seen(scene, [*a, *b]));
5573                    aborted[k] += counter::take_aborted();
5574                }
5575                assert_eq!(out[0], out[1], "round {round} step {step}");
5576            }
5577            stats.push((aborted, finite));
5578        }
5579        counter::set_near_limit(None);
5580        stats
5581    }
5582
5583    #[test]
5584    fn near_limit_matches_near_path() {
5585        // Recomputing every vertex once there is any near pair, against
5586        // always listing them.
5587        let stats = assert_twins_agree(0xb0b, 12, 0, usize::MAX);
5588        // The recompute path ran in every round that searches, and never
5589        // without a limit.
5590        let ran: Vec<bool> = stats.iter().map(|&([on, _], _)| on > 0).collect();
5591        let searching: Vec<bool> = stats.iter().map(|&(_, finite)| finite).collect();
5592        assert_eq!(ran, searching);
5593        let off: Vec<usize> = stats.iter().map(|&([_, off], _)| off).collect();
5594        assert_eq!(off, vec![0; stats.len()]);
5595        assert!(
5596            searching.iter().filter(|&&f| f).count() >= 10,
5597            "{searching:?}"
5598        );
5599    }
5600
5601    #[test]
5602    fn wide_reach_recomputes_every_vertex() {
5603        let mut scene = Scene::new(SceneOptions::default());
5604        // 61 x 61 = 3721 vertices, all within reach of the cube.
5605        let a = scene
5606            .add_mesh(sheet(60, 1.0 / 60.0), MeshOptions::default())
5607            .unwrap();
5608        let b = scene
5609            .add_mesh(cube([0.4, 0.4, -0.05], 0.1), MeshOptions::default())
5610            .unwrap();
5611        let f = scene.add_distance_field(field("d", a, b, 2.0)).unwrap();
5612        scene.update_fields().unwrap();
5613        counter::take();
5614        scene.set_placement(b, at(0.002, 0.001, 0.0)).unwrap();
5615        scene.update_fields().unwrap();
5616        assert_eq!(counter::take(), 3721);
5617        assert_matches(&mut scene, f);
5618    }
5619
5620    #[test]
5621    fn invalid_field_descs_rejected() {
5622        let (mut scene, a, b) = sheet_and_cube(UpdateMode::Eager);
5623        let bad = |e| matches!(e, Err(SceneError::InvalidInput(_)));
5624        assert!(bad(scene.add_distance_field(field("d", a, a, 0.1))));
5625        assert!(bad(scene.add_distance_field(field("d", a, b, -1.0))));
5626        assert!(bad(scene.add_distance_field(field("d", a, b, f64::NAN))));
5627        scene.add_distance_field(field("d", a, b, 0.1)).unwrap();
5628        assert!(bad(scene.add_distance_field(field("d", a, b, 0.2))));
5629        // The same name on another mesh is fine.
5630        scene.add_distance_field(field("d", b, a, 0.1)).unwrap();
5631        let gone = scene
5632            .add_mesh(sheet(1, 1.0), MeshOptions::default())
5633            .unwrap();
5634        scene.remove_mesh(gone).unwrap();
5635        assert_eq!(
5636            scene.add_distance_field(field("e", a, gone, 0.1)),
5637            Err(SceneError::UnknownMesh)
5638        );
5639        assert_eq!(
5640            scene.add_distance_field(field("e", gone, a, 0.1)),
5641            Err(SceneError::UnknownMesh)
5642        );
5643        assert_eq!(scene.fields.len(), 2);
5644        // The reserved channels are not reachable through the scene.
5645        assert!(matches!(
5646            scene.derived_channel_mut(a, "field:d"),
5647            Err(SceneError::InvalidInput(_))
5648        ));
5649    }
5650
5651    /// Steps units with `Points(budget)` until none is left, bringing stale
5652    /// Manual BVHs up to date when a step asks, as [`update_all`] does.
5653    /// Returns the steps that left a unit pending.
5654    fn step_all(scene: &mut Scene, budget: usize) -> usize {
5655        let mut pending = 0;
5656        for _ in 0..1_000_000 {
5657            match scene.field_step(FieldBudget::Points(budget)) {
5658                Ok(StepState::Idle) => {
5659                    assert!(!scene.field_step_pending());
5660                    return pending;
5661                }
5662                Ok(StepState::Pending) => {
5663                    assert!(scene.field_step_pending());
5664                    pending += 1;
5665                }
5666                Ok(StepState::Done) => assert!(!scene.field_step_pending()),
5667                Err(SceneError::StaleBvh(id)) => {
5668                    assert!(!scene.field_step_pending());
5669                    scene.update(id).unwrap();
5670                }
5671                Err(e) => panic!("field_step failed: {e}"),
5672            }
5673        }
5674        panic!("field_step never went idle");
5675    }
5676
5677    /// Opens a unit and steps it one candidate at a time, up to `points`
5678    /// candidates but leaving at least one to compute. Returns the
5679    /// candidates computed.
5680    fn step_partway(scene: &mut Scene, points: usize) -> usize {
5681        let state = scene.field_step(FieldBudget::Points(1)).unwrap();
5682        assert_eq!(state, StepState::Pending);
5683        let mut done = 1;
5684        while done < points && scene.field_step_left() > 1 {
5685            let state = scene.field_step(FieldBudget::Points(1)).unwrap();
5686            assert_eq!(state, StepState::Pending);
5687            done += 1;
5688        }
5689        assert!(scene.field_step_pending());
5690        done
5691    }
5692
5693    #[test]
5694    fn stepped_matches_one_shot() {
5695        // Four copies of each random scene, all deferring their Eager
5696        // fields: three are stepped (then `update_fields` brings the Lazy
5697        // and Manual fields along), the last only calls `update_fields`.
5698        let budgets = [1, 37, usize::MAX];
5699        let mut rng = Lcg(0x57e9);
5700        let mut spread = [0usize; 3];
5701        for round in 0..10 {
5702            let state = rng.0;
5703            let mut twins: Vec<_> = (0..4)
5704                .map(|_| {
5705                    let mut r = Lcg(state);
5706                    let (mut scene, a, b) = twin_scene(&mut r);
5707                    scene.set_defer_fields(true);
5708                    scene.set_mode(a, UpdateMode::Eager).unwrap();
5709                    scene.set_mode(b, UpdateMode::Eager).unwrap();
5710                    update_all(&mut scene);
5711                    scene.take_changes();
5712                    (r, scene, a, b)
5713                })
5714                .collect();
5715            rng.0 = twins[0].0.0;
5716            for step in 0..30 {
5717                let ops = 1 + rng.below(4);
5718                let mut out = Vec::new();
5719                for (k, (r, scene, a, b)) in twins.iter_mut().enumerate() {
5720                    for _ in 0..ops {
5721                        random_op(scene, *a, *b, r);
5722                    }
5723                    if let Some(&budget) = budgets.get(k) {
5724                        spread[k] += step_all(scene, budget);
5725                    }
5726                    update_all(scene);
5727                    out.push(seen(scene, [*a, *b]));
5728                }
5729                for k in 0..3 {
5730                    assert_eq!(
5731                        out[k], out[3],
5732                        "round {round} step {step} budget {}",
5733                        budgets[k]
5734                    );
5735                }
5736            }
5737        }
5738        // Small budgets left units open across steps; the largest never.
5739        assert!(spread[0] > spread[1] && spread[1] > 0, "{spread:?}");
5740        assert_eq!(spread[2], 0);
5741    }
5742
5743    /// [`step_all`], abandoning the unit at up to two random step
5744    /// boundaries (one in five) and stepping on, which opens a new one.
5745    /// Returns the units abandoned.
5746    fn step_all_abandoning(scene: &mut Scene, budget: usize, rng: &mut Lcg) -> usize {
5747        let mut abandoned = 0;
5748        for _ in 0..1_000_000 {
5749            match scene.field_step(FieldBudget::Points(budget)) {
5750                Ok(StepState::Idle) => return abandoned,
5751                Ok(StepState::Pending) => {
5752                    if abandoned < 2 && rng.below(5) == 0 {
5753                        scene.abandon_field_step();
5754                        assert!(!scene.field_step_pending());
5755                        abandoned += 1;
5756                    }
5757                }
5758                Ok(StepState::Done) => {}
5759                Err(SceneError::StaleBvh(id)) => scene.update(id).unwrap(),
5760                Err(e) => panic!("field_step failed: {e}"),
5761            }
5762        }
5763        panic!("field_step never went idle");
5764    }
5765
5766    #[test]
5767    fn stepped_with_abandons_matches_one_shot() {
5768        // As `stepped_matches_one_shot`, with units abandoned at random step
5769        // boundaries in the random walk and the stepping carried on.
5770        let budgets = [1, 37];
5771        let mut rng = Lcg(0xab4d);
5772        let mut abandoned = [0usize; 2];
5773        for round in 0..8 {
5774            let state = rng.0;
5775            let mut twins: Vec<_> = (0..3)
5776                .map(|k| {
5777                    let mut r = Lcg(state);
5778                    let (mut scene, a, b) = twin_scene(&mut r);
5779                    scene.set_defer_fields(true);
5780                    scene.set_mode(a, UpdateMode::Eager).unwrap();
5781                    scene.set_mode(b, UpdateMode::Eager).unwrap();
5782                    update_all(&mut scene);
5783                    scene.take_changes();
5784                    // Its own generator for when to abandon, so that the
5785                    // random walks stay the same.
5786                    (r, Lcg(state ^ (k as u64 + 1)), scene, a, b)
5787                })
5788                .collect();
5789            rng.0 = twins[0].0.0;
5790            for step in 0..30 {
5791                let ops = 1 + rng.below(4);
5792                let mut out = Vec::new();
5793                for (k, (r, when, scene, a, b)) in twins.iter_mut().enumerate() {
5794                    for _ in 0..ops {
5795                        random_op(scene, *a, *b, r);
5796                    }
5797                    if let Some(&budget) = budgets.get(k) {
5798                        abandoned[k] += step_all_abandoning(scene, budget, when);
5799                    }
5800                    update_all(scene);
5801                    out.push(seen(scene, [*a, *b]));
5802                }
5803                for k in 0..2 {
5804                    assert_eq!(
5805                        out[k], out[2],
5806                        "round {round} step {step} budget {}",
5807                        budgets[k]
5808                    );
5809                }
5810            }
5811        }
5812        assert!(abandoned.iter().all(|&n| n >= 20), "{abandoned:?}");
5813    }
5814
5815    #[test]
5816    fn step_error_stays_with_its_field() {
5817        let (mut scene, a, b, [fa, fb]) = deferred_pair(0.15);
5818        let (mut twin, ..) = deferred_pair(0.15);
5819        for s in [&mut scene, &mut twin] {
5820            s.set_placement(b, turned(6.0, [0.03, 0.02, 0.01])).unwrap();
5821            s.take_changes();
5822        }
5823        let before = bits(scene.field_values(fa).unwrap());
5824        // Computing A's field fails at every step; B's goes on.
5825        counter::set_failing(Some(fa));
5826        let mut states = Vec::new();
5827        let last = loop {
5828            match scene.field_step(FieldBudget::Points(7)) {
5829                Ok(StepState::Pending) => states.push(StepState::Pending),
5830                other => break other,
5831            }
5832        };
5833        counter::set_failing(None);
5834        assert!(!states.is_empty());
5835        assert_eq!(last, Err(SceneError::Store(StoreError::OutOfMemory)));
5836        assert!(!scene.field_step_pending());
5837        // A's field is left as it was, stale and due; B's field applied.
5838        assert_eq!(bits(scene.field_values(fa).unwrap()), before);
5839        assert!(!scene.field_status(fa).unwrap().current);
5840        assert!(scene.field_status(fb).unwrap().current);
5841        twin.update_fields().unwrap();
5842        assert_eq!(
5843            bits(scene.field_values(fb).unwrap()),
5844            bits(twin.field_values(fb).unwrap())
5845        );
5846        let ch = scene.take_changes();
5847        assert!(ch.iter().all(|c| c.id != a), "A's field reported");
5848        // The next unit brings A's field along.
5849        assert!(step_all(&mut scene, 7) > 0);
5850        twin.take_changes();
5851        assert_eq!(seen_values(&scene, [a, b]), seen_values(&twin, [a, b]));
5852        // With B's field failing in a unit computed in one call, that call
5853        // applies A's field and returns the error.
5854        for s in [&mut scene, &mut twin] {
5855            s.set_placement(b, turned(-4.0, [0.01, 0.0, 0.02])).unwrap();
5856        }
5857        counter::set_failing(Some(fb));
5858        assert_eq!(
5859            scene.field_step(FieldBudget::Points(usize::MAX)),
5860            Err(SceneError::Store(StoreError::OutOfMemory))
5861        );
5862        counter::set_failing(None);
5863        assert!(scene.field_status(fa).unwrap().current);
5864        assert!(!scene.field_status(fb).unwrap().current);
5865        step_all(&mut scene, usize::MAX);
5866        twin.update_fields().unwrap();
5867        assert_eq!(seen(&mut scene, [a, b]).0, seen(&mut twin, [a, b]).0);
5868    }
5869
5870    #[test]
5871    fn stepping_runs_parallel_paths() {
5872        use fast_mesh_store::par;
5873        // Two sheets of 6,561 vertices each, every vertex recomputed (an
5874        // infinite max_distance): enough for runs of 2,048 to compute in
5875        // parallel chunks.
5876        let make = || {
5877            let mut scene = Scene::new(SceneOptions::default());
5878            let a = scene
5879                .add_mesh(sheet(80, 1.0 / 80.0), MeshOptions::default())
5880                .unwrap();
5881            let opts = MeshOptions {
5882                placement: at(0.0, 0.0, 0.05),
5883                ..MeshOptions::default()
5884            };
5885            let b = scene.add_mesh(sheet(80, 1.0 / 80.0), opts).unwrap();
5886            for (on, to) in [(a, b), (b, a)] {
5887                let id = scene
5888                    .add_distance_field(field("d", on, to, f64::INFINITY))
5889                    .unwrap();
5890                let rule = distance_rule(0.1, 0.1);
5891                scene
5892                    .set_coloring(on, Some(Coloring { field: id, rule }))
5893                    .unwrap();
5894            }
5895            scene.set_defer_fields(true);
5896            scene
5897                .set_placement(b, turned(3.0, [0.01, 0.0, 0.04]))
5898                .unwrap();
5899            scene.take_changes();
5900            (scene, a, b)
5901        };
5902        let (mut scene, a, b) = make();
5903        let (mut twin, ..) = make();
5904        counter::take_parallel();
5905        let entries = par::parallel_entries();
5906        assert_eq!(
5907            scene.field_step(FieldBudget::Points(1)),
5908            Ok(StepState::Pending)
5909        );
5910        // The first step chooses A's candidates (every vertex slot, a step
5911        // of its own); fields are stepped one after the other, never as a
5912        // parallel batch.
5913        assert_eq!(counter::take_parallel(), 0);
5914        let opened = par::parallel_entries();
5915        assert_eq!(opened, entries);
5916        assert_eq!(scene.field_step_left(), 6561);
5917        assert_eq!(scene.field_step_stage(), Some(Stage::Values));
5918        assert_eq!(
5919            scene.field_step(FieldBudget::Points(2048)),
5920            Ok(StepState::Pending)
5921        );
5922        assert_eq!(scene.field_step_left(), 6561 - 2048);
5923        let chunked = par::parallel_entries() - opened;
5924        if par::enabled() {
5925            assert!(chunked > 0, "values computed sequentially");
5926        } else {
5927            assert_eq!(chunked, 0);
5928        }
5929        assert!(step_all(&mut scene, 2048) > 0);
5930        twin.update_fields().unwrap();
5931        assert_eq!(seen(&mut scene, [a, b]), seen(&mut twin, [a, b]));
5932    }
5933
5934    /// A sheet A and a cube B with a field each way, both coloured, Eager
5935    /// meshes deferring their fields, everything current and drained.
5936    fn deferred_pair(max_distance: f64) -> (Scene, MeshId, MeshId, [FieldId; 2]) {
5937        deferred_pair_signed(max_distance, SignMode::Auto)
5938    }
5939
5940    /// [`deferred_pair`], with the fields' sign mode. Winding sign data have no
5941    /// refit, so that an edit of a target always builds them in steps.
5942    fn deferred_pair_signed(
5943        max_distance: f64,
5944        sign: SignMode,
5945    ) -> (Scene, MeshId, MeshId, [FieldId; 2]) {
5946        let (mut scene, a, b) = sheet_and_cube(UpdateMode::Eager);
5947        let signed = |mut d: DistanceField| {
5948            d.sign = sign;
5949            d
5950        };
5951        let fa = scene
5952            .add_distance_field(signed(field("d", a, b, max_distance)))
5953            .unwrap();
5954        let fb = scene
5955            .add_distance_field(signed(field("d", b, a, max_distance)))
5956            .unwrap();
5957        for (m, f, rule) in [(a, fa, distance_rule(0.2, 0.1)), (b, fb, contact_rule(0.1))] {
5958            scene
5959                .set_coloring(m, Some(Coloring { field: f, rule }))
5960                .unwrap();
5961        }
5962        scene.set_defer_fields(true);
5963        scene.take_changes();
5964        (scene, a, b, [fa, fb])
5965    }
5966
5967    #[test]
5968    fn abandon_leaves_no_trace() {
5969        // 121 sheet vertices, then 8 cube vertices: partway into the first
5970        // field, at its end, and into the second.
5971        for md in [0.15, f64::INFINITY] {
5972            for points in [1, 60, 121, 125] {
5973                let at_ = format!("max_distance {md}, {points} points");
5974                let (mut scene, a, b, ids) = deferred_pair(md);
5975                let (mut twin, ..) = deferred_pair(md);
5976                for s in [&mut scene, &mut twin] {
5977                    s.set_placement(b, turned(10.0, [0.05, -0.02, 0.03]))
5978                        .unwrap();
5979                    s.take_changes();
5980                }
5981                let before = seen(&mut scene, [a, b]);
5982                step_partway(&mut scene, points);
5983                assert_eq!(scene.field_step_meshes(), vec![a, b], "{at_}");
5984                // Reading does not abandon.
5985                assert_eq!(
5986                    seen_values(&scene, [a, b]),
5987                    (before.0.clone(), before.1.clone())
5988                );
5989                assert!(scene.field_step_pending(), "{at_}");
5990                scene.abandon_field_step();
5991                assert!(!scene.field_step_pending(), "{at_}");
5992                assert!(scene.field_step_meshes().is_empty(), "{at_}");
5993                // Abandoning when idle does nothing.
5994                scene.abandon_field_step();
5995                assert_eq!(seen(&mut scene, [a, b]), before, "{at_}");
5996                for id in ids {
5997                    assert!(!scene.field_status(id).unwrap().current, "{at_}");
5998                }
5999                scene.update_fields().unwrap();
6000                twin.update_fields().unwrap();
6001                assert_eq!(seen(&mut scene, [a, b]), seen(&mut twin, [a, b]), "{at_}");
6002            }
6003        }
6004    }
6005
6006    /// The values and colours of [`Seen`].
6007    type Shown = (Vec<Vec<u32>>, Vec<Option<Vec<[u8; 4]>>>);
6008
6009    /// The values and colours of [`seen`], without draining the reports.
6010    fn seen_values(scene: &Scene, meshes: [MeshId; 2]) -> Shown {
6011        let values = scene
6012            .fields
6013            .iter()
6014            .map(|f| bits(scene.field_values(f.id).unwrap()))
6015            .collect();
6016        let colors = meshes
6017            .iter()
6018            .map(|&m| scene.colors(m).unwrap().map(<[_]>::to_vec))
6019            .collect();
6020        (values, colors)
6021    }
6022
6023    #[test]
6024    fn abandon_then_edit_matches_one_shot() {
6025        let edits: [fn(&mut Scene, MeshId, MeshId); 3] = [
6026            |s, a, _| {
6027                s.edit(a, |tx| tx.set_positions(&[45], &[[0.5, 0.4, -0.05]]))
6028                    .unwrap();
6029            },
6030            |s, _, b| {
6031                s.edit(b, |tx| tx.set_positions(&[7], &[[0.75, 0.75, 0.25]]))
6032                    .unwrap();
6033            },
6034            |s, a, _| {
6035                s.edit(a, |tx| tx.remove_triangles(&[0, 1, 2])).unwrap();
6036            },
6037        ];
6038        for md in [0.15, f64::INFINITY] {
6039            for (e, edit) in edits.iter().enumerate() {
6040                // Abandoned by hand, or by the edit itself.
6041                for by_hand in [true, false] {
6042                    let at_ = format!("max_distance {md}, edit {e}, by hand {by_hand}");
6043                    let (mut scene, a, b, _) = deferred_pair(md);
6044                    let (mut twin, ..) = deferred_pair(md);
6045                    let p1 = turned(-8.0, [0.04, 0.03, 0.02]);
6046                    scene.set_placement(b, p1).unwrap();
6047                    step_partway(&mut scene, 70);
6048                    if by_hand {
6049                        scene.abandon_field_step();
6050                    }
6051                    edit(&mut scene, a, b);
6052                    assert!(!scene.field_step_pending(), "{at_}");
6053                    step_all(&mut scene, 37);
6054                    twin.set_placement(b, p1).unwrap();
6055                    edit(&mut twin, a, b);
6056                    twin.update_fields().unwrap();
6057                    assert_eq!(seen(&mut scene, [a, b]), seen(&mut twin, [a, b]), "{at_}");
6058                    // Nothing of P1 alone: the values are those of a fresh
6059                    // computation at the final state.
6060                    for m in [a, b] {
6061                        scene.update(m).unwrap();
6062                    }
6063                    for f in scene.fields.iter().map(|f| f.id).collect::<Vec<_>>() {
6064                        assert_matches(&mut scene, f);
6065                    }
6066                }
6067            }
6068        }
6069    }
6070
6071    #[test]
6072    fn thickness_plate_reads_depth() {
6073        // The interior face vertices of a closed plate read its depth.
6074        let mut scene = Scene::new(SceneOptions::default());
6075        let m = scene.add_mesh(plate(4), MeshOptions::default()).unwrap();
6076        let id = scene
6077            .add_thickness_field(thick("t", m, f64::INFINITY))
6078            .unwrap();
6079        assert!(scene.field_status(id).unwrap().current);
6080        let values = scene.field_values(id).unwrap();
6081        assert_eq!(values.len(), scene.mesh(m).unwrap().vertex_slots());
6082        let w = 5;
6083        for y in 1..4 {
6084            for x in 1..4 {
6085                for &v in &[y * w + x, w * w + y * w + x] {
6086                    assert!((values[v as usize] - 0.5).abs() < 1e-3, "{values:?}");
6087                }
6088            }
6089        }
6090        // No targets: nearest and target lists are empty, status is the
6091        // placeholder, and the field lists on its mesh.
6092        assert_eq!(scene.field_nearest(id).unwrap(), &[]);
6093        assert_eq!(scene.field_targets(id).unwrap(), &[]);
6094        assert!(scene.field_target_status(id).unwrap().is_empty());
6095        assert_eq!(scene.fields_on(m).unwrap(), vec![id]);
6096        // Colouring by it works like any field.
6097        let rule = thick_rule(0.0, 1.0);
6098        scene
6099            .set_coloring(
6100                m,
6101                Some(Coloring {
6102                    field: id,
6103                    rule: rule.clone(),
6104                }),
6105            )
6106            .unwrap();
6107        assert_colors(&scene, m, &rule, id);
6108    }
6109
6110    #[test]
6111    fn thickness_open_sheet_reads_nan() {
6112        // An open sheet has no opposite surface: every vertex reads NaN.
6113        let mut scene = Scene::new(SceneOptions::default());
6114        let m = scene
6115            .add_mesh(sheet(4, 0.5), MeshOptions::default())
6116            .unwrap();
6117        let id = scene
6118            .add_thickness_field(thick("t", m, f64::INFINITY))
6119            .unwrap();
6120        let values = scene.field_values(id).unwrap();
6121        assert!(!values.is_empty());
6122        assert!(values.iter().all(|v| v.is_nan()), "{values:?}");
6123        // NaN colours show the rule's nan colour.
6124        let rule = thick_rule(0.0, 1.0);
6125        scene
6126            .set_coloring(
6127                m,
6128                Some(Coloring {
6129                    field: id,
6130                    rule: rule.clone(),
6131                }),
6132            )
6133            .unwrap();
6134        assert_colors(&scene, m, &rule, id);
6135        for c in scene.colors(m).unwrap().unwrap() {
6136            assert_eq!(*c, rule.nan);
6137        }
6138    }
6139
6140    #[test]
6141    fn thickness_max_distance_reads_far() {
6142        let mut scene = Scene::new(SceneOptions::default());
6143        let m = scene.add_mesh(plate(2), MeshOptions::default()).unwrap();
6144        let id = scene.add_thickness_field(thick("t", m, 0.1)).unwrap();
6145        assert!(
6146            scene
6147                .field_values(id)
6148                .unwrap()
6149                .iter()
6150                .all(|&v| v == f32::INFINITY)
6151        );
6152        // Reconfiguring widens the bound (the sign is ignored: thickness
6153        // has none).
6154        scene
6155            .reconfigure_field(id, f64::INFINITY, SignMode::Winding)
6156            .unwrap();
6157        let values = scene.field_values(id).unwrap();
6158        assert!(values.iter().all(|v| v.is_finite()), "{values:?}");
6159        // And back down again.
6160        scene
6161            .reconfigure_field(id, 0.1, SignMode::Pseudonormal)
6162            .unwrap();
6163        assert!(
6164            scene
6165                .field_values(id)
6166                .unwrap()
6167                .iter()
6168                .all(|&v| v == f32::INFINITY)
6169        );
6170        assert!(scene.field_status(id).unwrap().current);
6171    }
6172
6173    #[test]
6174    fn thickness_validation() {
6175        let mut scene = Scene::new(SceneOptions::default());
6176        let m = scene.add_mesh(plate(1), MeshOptions::default()).unwrap();
6177        scene.add_thickness_field(thick("t", m, 1.0)).unwrap();
6178        assert!(matches!(
6179            scene.add_thickness_field(thick("t", m, 1.0)),
6180            Err(SceneError::InvalidInput(_))
6181        ));
6182        assert!(matches!(
6183            scene.add_distance_field(field("t", m, m, 1.0)),
6184            Err(SceneError::InvalidInput(_))
6185        ));
6186        assert!(matches!(
6187            scene.add_thickness_field(ThicknessField {
6188                name: "u".into(),
6189                on: m,
6190                max_distance: -1.0,
6191            }),
6192            Err(SceneError::InvalidInput(_))
6193        ));
6194        assert!(matches!(
6195            scene.add_thickness_field(ThicknessField {
6196                name: "u".into(),
6197                on: m,
6198                max_distance: f64::NAN,
6199            }),
6200            Err(SceneError::InvalidInput(_))
6201        ));
6202        // Withdrawing a computed field, or one its mesh colours by, refuses.
6203        let id = scene.field_id(0).unwrap();
6204        let rule = thick_rule(0.0, 1.0);
6205        scene
6206            .set_coloring(m, Some(Coloring { field: id, rule }))
6207            .unwrap();
6208        assert!(!scene.withdraw_field(id).unwrap());
6209        scene.set_coloring(m, None).unwrap();
6210        assert!(!scene.withdraw_field(id).unwrap());
6211        scene.remove_field(id).unwrap();
6212        assert!(matches!(
6213            scene.field_values(id),
6214            Err(SceneError::UnknownField)
6215        ));
6216        // A deferred field withdraws before it computes.
6217        let late = scene
6218            .add_thickness_field_deferred(thick("late", m, 1.0))
6219            .unwrap();
6220        assert!(scene.withdraw_field(late).unwrap());
6221        assert!(matches!(
6222            scene.field_values(late),
6223            Err(SceneError::UnknownField)
6224        ));
6225    }
6226
6227    #[test]
6228    fn thickness_follows_edits_and_placements() {
6229        let mut scene = Scene::new(SceneOptions::default());
6230        let m = scene.add_mesh(plate(2), MeshOptions::default()).unwrap();
6231        let id = scene
6232            .add_thickness_field(thick("t", m, f64::INFINITY))
6233            .unwrap();
6234        // The interior face vertices read the depth, 0.5.
6235        let interior = [4, 9 + 4];
6236        for &v in &interior {
6237            assert!((scene.field_values(id).unwrap()[v as usize] - 0.5).abs() < 1e-3);
6238        }
6239        // A uniform scale multiplies the world thicknesses.
6240        scene
6241            .set_placement(
6242                m,
6243                Placement {
6244                    scale: [2.0, 2.0, 2.0],
6245                    ..Placement::IDENTITY
6246                },
6247            )
6248            .unwrap();
6249        for &v in &interior {
6250            assert!((scene.field_values(id).unwrap()[v as usize] - 1.0).abs() < 1e-3);
6251        }
6252        // A non-uniform scale bakes the mesh: doubling z doubles the depth
6253        // under the untilted interior normals.
6254        scene
6255            .set_placement(
6256                m,
6257                Placement {
6258                    scale: [1.0, 1.0, 2.0],
6259                    ..Placement::IDENTITY
6260                },
6261            )
6262            .unwrap();
6263        for &v in &interior {
6264            assert!((scene.field_values(id).unwrap()[v as usize] - 1.0).abs() < 1e-3);
6265        }
6266        scene.set_placement(m, Placement::IDENTITY).unwrap();
6267        // Raising a top vertex reshapes the plate and its thicknesses.
6268        let before = scene.field_values(id).unwrap().to_vec();
6269        scene
6270            .edit(m, |tx| tx.set_positions(&[1], &[[1.0 / 2.0, 0.0, 1.0]]))
6271            .unwrap();
6272        assert!(scene.field_status(id).unwrap().current);
6273        let after = scene.field_values(id).unwrap().to_vec();
6274        assert_ne!(before, after);
6275        assert!(after.iter().all(|v| v.is_finite()));
6276    }
6277
6278    #[test]
6279    fn thickness_steps_in_full() {
6280        let mut scene = Scene::new(SceneOptions::default());
6281        let m = scene.add_mesh(plate(2), MeshOptions::default()).unwrap();
6282        let id = scene
6283            .add_thickness_field_deferred(thick("t", m, f64::INFINITY))
6284            .unwrap();
6285        assert!(!scene.field_status(id).unwrap().current);
6286        assert_eq!(
6287            scene.field_step_field(id, FieldBudget::Points(1)),
6288            Ok(StepState::Done)
6289        );
6290        assert!(scene.field_status(id).unwrap().current);
6291        assert!(
6292            scene
6293                .field_values(id)
6294                .unwrap()
6295                .iter()
6296                .all(|v| v.is_finite())
6297        );
6298        // Stepping a current field idles.
6299        assert_eq!(
6300            scene.field_step_field(id, FieldBudget::Points(1)),
6301            Ok(StepState::Idle)
6302        );
6303        // A Lazy field computes on flush.
6304        let mut lazy = Scene::new(SceneOptions::default());
6305        let lm = lazy
6306            .add_mesh(
6307                plate(2),
6308                MeshOptions {
6309                    mode: UpdateMode::Lazy,
6310                    ..MeshOptions::default()
6311                },
6312            )
6313            .unwrap();
6314        let lid = lazy
6315            .add_thickness_field(thick("t", lm, f64::INFINITY))
6316            .unwrap();
6317        assert!(!lazy.field_status(lid).unwrap().current);
6318        lazy.flush().unwrap();
6319        assert!(lazy.field_status(lid).unwrap().current);
6320        assert!(
6321            lazy.field_values(lid)
6322                .unwrap()
6323                .iter()
6324                .all(|v| v.is_finite())
6325        );
6326    }
6327
6328    #[test]
6329    fn curvature_sphere_reads_one_over_r() {
6330        // Every vertex of the unit ball reads about 1, positive (a dome from
6331        // outside).
6332        let mut scene = Scene::new(SceneOptions::default());
6333        let m = scene.add_mesh(ball(), MeshOptions::default()).unwrap();
6334        let id = scene.add_curvature_field(curv("k", m)).unwrap();
6335        assert!(scene.field_status(id).unwrap().current);
6336        let values = scene.field_values(id).unwrap();
6337        assert_eq!(values.len(), scene.mesh(m).unwrap().vertex_slots());
6338        for &v in values.iter() {
6339            assert!(v.is_finite(), "{values:?}");
6340            assert!(v > 0.0, "{values:?}");
6341        }
6342        let mean = values.iter().sum::<f32>() / values.len() as f32;
6343        assert!((mean - 1.0).abs() < 0.3, "{mean}");
6344        // No targets: nearest and target lists are empty, status is the
6345        // placeholder, and the field lists on its mesh.
6346        assert_eq!(scene.field_nearest(id).unwrap(), &[]);
6347        assert_eq!(scene.field_targets(id).unwrap(), &[]);
6348        assert!(scene.field_target_status(id).unwrap().is_empty());
6349        assert_eq!(scene.fields_on(m).unwrap(), vec![id]);
6350        // Colouring by it works like any field.
6351        let rule = thick_rule(-1.0, 2.0);
6352        scene
6353            .set_coloring(
6354                m,
6355                Some(Coloring {
6356                    field: id,
6357                    rule: rule.clone(),
6358                }),
6359            )
6360            .unwrap();
6361        assert_colors(&scene, m, &rule, id);
6362    }
6363
6364    #[test]
6365    fn curvature_plane_reads_zero_and_saddle_negative() {
6366        let mut scene = Scene::new(SceneOptions::default());
6367        // A flat sheet reads 0 everywhere, boundary fan included.
6368        let flat = scene
6369            .add_mesh(sheet(8, 0.25), MeshOptions::default())
6370            .unwrap();
6371        let fid = scene.add_curvature_field(curv("k", flat)).unwrap();
6372        for &v in scene.field_values(fid).unwrap().iter() {
6373            assert!(v.is_finite());
6374            assert!(v.abs() < 1e-5, "{v}");
6375        }
6376        // A saddle reads the sign of its dominant bend: about -1/s at the
6377        // middle, finite at the one-sided rim.
6378        let m = scene
6379            .add_mesh(saddle(32, 4.0), MeshOptions::default())
6380            .unwrap();
6381        let id = scene.add_curvature_field(curv("k", m)).unwrap();
6382        let values = scene.field_values(id).unwrap();
6383        assert!(values.iter().all(|v| v.is_finite()), "{values:?}");
6384        let c = (16 * 33 + 16) as usize;
6385        assert!((values[c] + 0.25).abs() < 0.15, "{:?}", values[c]);
6386        assert!(values.iter().any(|&v| v < -0.1), "{values:?}");
6387        // NaN colours show the rule's nan colour on dead slots (none here,
6388        // but the rule still applies).
6389        let rule = thick_rule(-1.0, 1.0);
6390        scene
6391            .set_coloring(
6392                m,
6393                Some(Coloring {
6394                    field: id,
6395                    rule: rule.clone(),
6396                }),
6397            )
6398            .unwrap();
6399        assert_colors(&scene, m, &rule, id);
6400    }
6401
6402    #[test]
6403    fn curvature_follows_scales() {
6404        let mut scene = Scene::new(SceneOptions::default());
6405        let m = scene.add_mesh(ball(), MeshOptions::default()).unwrap();
6406        let id = scene.add_curvature_field(curv("k", m)).unwrap();
6407        let local = scene.field_values(id).unwrap().to_vec();
6408        // A uniform scale of 2 halves every curvature (1/world-unit).
6409        scene
6410            .set_placement(
6411                m,
6412                Placement {
6413                    scale: [2.0, 2.0, 2.0],
6414                    ..Placement::IDENTITY
6415                },
6416            )
6417            .unwrap();
6418        let scaled = scene.field_values(id).unwrap().to_vec();
6419        assert_eq!(scaled.len(), local.len());
6420        for (&x, &y) in scaled.iter().zip(&local) {
6421            assert!((x - y / 2.0).abs() < 1e-4, "{x} vs {y}");
6422        }
6423        assert!(scaled.iter().all(|v| v.is_finite()));
6424        // A non-uniform scale bakes a world-frame copy: still finite and
6425        // current (an ellipsoid is flatter at its equator than a sphere).
6426        scene
6427            .set_placement(
6428                m,
6429                Placement {
6430                    scale: [1.0, 1.0, 2.0],
6431                    ..Placement::IDENTITY
6432                },
6433            )
6434            .unwrap();
6435        assert!(scene.field_status(id).unwrap().current);
6436        let baked = scene.field_values(id).unwrap().to_vec();
6437        assert!(baked.iter().all(|v| v.is_finite()), "{baked:?}");
6438        assert_ne!(baked, scaled);
6439        // Back to rigid: the local values return.
6440        scene.set_placement(m, Placement::IDENTITY).unwrap();
6441        assert_eq!(scene.field_values(id).unwrap(), &local);
6442    }
6443
6444    #[test]
6445    fn curvature_validation() {
6446        let mut scene = Scene::new(SceneOptions::default());
6447        let m = scene.add_mesh(ball(), MeshOptions::default()).unwrap();
6448        scene.add_thickness_field(thick("t", m, 1.0)).unwrap();
6449        scene.add_curvature_field(curv("k", m)).unwrap();
6450        // Names clash across all three registries.
6451        assert!(matches!(
6452            scene.add_curvature_field(curv("t", m)),
6453            Err(SceneError::InvalidInput(_))
6454        ));
6455        assert!(matches!(
6456            scene.add_curvature_field(curv("k", m)),
6457            Err(SceneError::InvalidInput(_))
6458        ));
6459        assert!(matches!(
6460            scene.add_thickness_field(thick("k", m, 1.0)),
6461            Err(SceneError::InvalidInput(_))
6462        ));
6463        // Reconfiguring marks the field for recompute (both settings are
6464        // ignored: a curvature has neither).
6465        let id = scene.field_id(1).unwrap();
6466        scene.reconfigure_field(id, 0.5, SignMode::Winding).unwrap();
6467        assert!(scene.field_status(id).unwrap().current);
6468        assert!(
6469            scene
6470                .field_values(id)
6471                .unwrap()
6472                .iter()
6473                .all(|v| v.is_finite())
6474        );
6475        // Removing the mesh removes the field.
6476        scene.remove_mesh(m).unwrap();
6477        assert!(matches!(
6478            scene.field_values(id),
6479            Err(SceneError::UnknownField)
6480        ));
6481        // A deferred field withdraws before it computes.
6482        let m2 = scene.add_mesh(ball(), MeshOptions::default()).unwrap();
6483        let late = scene
6484            .add_curvature_field_deferred(curv("late", m2))
6485            .unwrap();
6486        assert!(scene.withdraw_field(late).unwrap());
6487        assert!(matches!(
6488            scene.field_values(late),
6489            Err(SceneError::UnknownField)
6490        ));
6491    }
6492
6493    #[test]
6494    fn curvature_steps_in_full() {
6495        let mut scene = Scene::new(SceneOptions::default());
6496        let m = scene.add_mesh(ball(), MeshOptions::default()).unwrap();
6497        let id = scene.add_curvature_field_deferred(curv("k", m)).unwrap();
6498        assert!(!scene.field_status(id).unwrap().current);
6499        assert_eq!(
6500            scene.field_step_field(id, FieldBudget::Points(1)),
6501            Ok(StepState::Done)
6502        );
6503        assert!(scene.field_status(id).unwrap().current);
6504        assert!(
6505            scene
6506                .field_values(id)
6507                .unwrap()
6508                .iter()
6509                .all(|v| v.is_finite())
6510        );
6511        // Stepping a current field idles.
6512        assert_eq!(
6513            scene.field_step_field(id, FieldBudget::Points(1)),
6514            Ok(StepState::Idle)
6515        );
6516        // A Lazy field computes on flush.
6517        let mut lazy = Scene::new(SceneOptions::default());
6518        let lm = lazy
6519            .add_mesh(
6520                ball(),
6521                MeshOptions {
6522                    mode: UpdateMode::Lazy,
6523                    ..MeshOptions::default()
6524                },
6525            )
6526            .unwrap();
6527        let lid = lazy.add_curvature_field(curv("k", lm)).unwrap();
6528        assert!(!lazy.field_status(lid).unwrap().current);
6529        lazy.flush().unwrap();
6530        assert!(lazy.field_status(lid).unwrap().current);
6531        assert!(
6532            lazy.field_values(lid)
6533                .unwrap()
6534                .iter()
6535                .all(|v| v.is_finite())
6536        );
6537    }
6538
6539    #[test]
6540    fn mutation_abandons_open_unit() {
6541        let (mut scene, a, b, _) = deferred_pair(0.15);
6542        let (mut twin, ..) = deferred_pair(0.15);
6543        let far = MeshOptions {
6544            placement: at(5.0, 0.0, 0.0),
6545            ..MeshOptions::default()
6546        };
6547        let c = scene.add_mesh(sheet(2, 0.5), far).unwrap();
6548        twin.add_mesh(sheet(2, 0.5), far).unwrap();
6549        // An unrelated placement abandons the unit at P1; the unit stepped
6550        // afterwards computes P1 with the new placement.
6551        let p1 = turned(5.0, [0.02, 0.01, 0.0]);
6552        scene.set_placement(b, p1).unwrap();
6553        step_partway(&mut scene, 30);
6554        scene.set_placement(c, at(6.0, 0.0, 0.0)).unwrap();
6555        assert!(!scene.field_step_pending());
6556        step_all(&mut scene, 37);
6557        twin.set_placement(b, p1).unwrap();
6558        twin.set_placement(c, at(6.0, 0.0, 0.0)).unwrap();
6559        twin.update_fields().unwrap();
6560        assert_eq!(seen(&mut scene, [a, b]), seen(&mut twin, [a, b]));
6561        // A newer pose of B abandons the unit at P2; the next steps compute
6562        // P3.
6563        let (p2, p3) = (at(0.1, 0.0, 0.05), at(-0.05, 0.02, -0.03));
6564        scene.set_placement(b, p2).unwrap();
6565        step_partway(&mut scene, 100);
6566        scene.set_placement(b, p3).unwrap();
6567        assert!(!scene.field_step_pending());
6568        step_all(&mut scene, 37);
6569        twin.set_placement(b, p2).unwrap();
6570        twin.set_placement(b, p3).unwrap();
6571        twin.update_fields().unwrap();
6572        assert_eq!(seen(&mut scene, [a, b]), seen(&mut twin, [a, b]));
6573    }
6574
6575    /// What [`mutating_calls_abandon_open_unit`] needs of its scene.
6576    struct Ctx {
6577        a: MeshId,
6578        b: MeshId,
6579        c: MeshId,
6580        field: FieldId,
6581        section: crate::SectionId,
6582        subscriber: crate::SubscriberId,
6583    }
6584
6585    /// A scene whose field unit is open: a sheet A and a cube B with a
6586    /// field A to B, another sheet C far away with a channel `x` and a
6587    /// section, and a subscriber.
6588    fn open_unit() -> (Scene, Ctx) {
6589        let (mut scene, a, b) = sheet_and_cube(UpdateMode::Eager);
6590        let field = scene
6591            .add_distance_field(field("d", a, b, f64::INFINITY))
6592            .unwrap();
6593        let opts = MeshOptions {
6594            placement: at(5.0, 0.0, 0.0),
6595            ..MeshOptions::default()
6596        };
6597        let c = scene.add_mesh(sheet(2, 0.5), opts).unwrap();
6598        scene.add_channel(c, ChannelDesc::derived("x", 1)).unwrap();
6599        let plane = crate::Plane::new([0.0, 0.0, 0.0], [0.0, 0.0, 1.0]).unwrap();
6600        let section = scene
6601            .add_section(crate::SectionDesc {
6602                plane,
6603                meshes: vec![c],
6604                fill: false,
6605            })
6606            .unwrap();
6607        let subscriber = scene.subscribe();
6608        scene.set_defer_fields(true);
6609        scene.set_placement(b, at(0.01, 0.0, 0.0)).unwrap();
6610        step_partway(&mut scene, 1);
6611        let ctx = Ctx {
6612            a,
6613            b,
6614            c,
6615            field,
6616            section,
6617            subscriber,
6618        };
6619        (scene, ctx)
6620    }
6621
6622    #[test]
6623    fn mutating_calls_abandon_open_unit() {
6624        use crate::{ContactFilter, Plane, SceneClosestOptions, SceneRayOptions, SectionDesc};
6625        type Call = fn(&mut Scene, &Ctx);
6626        // Every public `&mut self` method of `Scene` but the three step
6627        // calls.
6628        let calls: [(&str, Call); 43] = [
6629            ("add_mesh", |s, _| {
6630                s.add_mesh(sheet(1, 1.0), MeshOptions::default()).unwrap();
6631            }),
6632            ("add_mesh_built", |s, _| {
6633                let build = crate::MeshBuild::new(sheet(1, 1.0), MeshOptions::default()).unwrap();
6634                s.add_mesh_built(build).unwrap();
6635            }),
6636            ("remove_mesh", |s, x| {
6637                s.remove_mesh(x.c).unwrap();
6638            }),
6639            ("edit", |s, x| {
6640                s.edit(x.c, |tx| tx.set_positions(&[0], &[[0.0, 0.0, 0.1]]))
6641                    .unwrap();
6642            }),
6643            ("begin_step", |s, _| s.begin_step()),
6644            ("end_step", |s, _| {
6645                let _ = s.end_step();
6646            }),
6647            ("cancel_step", |s, _| {
6648                let _ = s.cancel_step();
6649            }),
6650            ("undo", |s, _| {
6651                s.undo().unwrap();
6652            }),
6653            ("redo", |s, _| {
6654                s.redo().unwrap();
6655            }),
6656            ("clear_history", |s, _| s.clear_history().unwrap()),
6657            ("set_mode", |s, x| {
6658                s.set_mode(x.c, UpdateMode::Lazy).unwrap()
6659            }),
6660            ("update", |s, x| s.update(x.c).unwrap()),
6661            ("flush", |s, _| s.flush().unwrap()),
6662            ("take_changes", |s, _| {
6663                s.take_changes();
6664            }),
6665            ("subscribe", |s, _| {
6666                s.subscribe();
6667            }),
6668            ("unsubscribe", |s, x| s.unsubscribe(x.subscriber).unwrap()),
6669            ("take_changes_for", |s, x| {
6670                s.take_changes_for(x.subscriber).unwrap();
6671            }),
6672            ("add_channel", |s, x| {
6673                s.add_channel(x.c, ChannelDesc::derived("y", 1)).unwrap();
6674            }),
6675            ("remove_channel", |s, x| {
6676                s.remove_channel(x.c, "x").unwrap();
6677            }),
6678            ("derived_channel_mut", |s, x| {
6679                s.derived_channel_mut(x.c, "x").unwrap();
6680            }),
6681            ("compact", |s, x| {
6682                s.compact(x.c).unwrap();
6683            }),
6684            ("set_placement", |s, x| {
6685                s.set_placement(x.c, at(6.0, 0.0, 0.0)).unwrap();
6686            }),
6687            ("add_distance_field", |s, x| {
6688                s.add_distance_field(field("e", x.c, x.b, 0.1)).unwrap();
6689            }),
6690            ("add_distance_field_deferred", |s, x| {
6691                s.add_distance_field_deferred(field("e", x.c, x.b, 0.1))
6692                    .unwrap();
6693            }),
6694            ("add_thickness_field", |s, x| {
6695                s.add_thickness_field(thick("e", x.c, f64::INFINITY))
6696                    .unwrap();
6697            }),
6698            ("add_thickness_field_deferred", |s, x| {
6699                s.add_thickness_field_deferred(thick("e", x.c, f64::INFINITY))
6700                    .unwrap();
6701            }),
6702            ("withdraw_field", |s, x| {
6703                assert!(!s.withdraw_field(x.field).unwrap());
6704            }),
6705            ("remove_field", |s, x| s.remove_field(x.field).unwrap()),
6706            ("update_fields", |s, _| s.update_fields().unwrap()),
6707            ("set_coloring", |s, x| s.set_coloring(x.c, None).unwrap()),
6708            ("set_normals", |s, x| s.set_normals(x.c, true).unwrap()),
6709            ("raycast", |s, _| {
6710                let opts = SceneRayOptions::default();
6711                s.raycast([0.5, 0.5, 1.0], [0.0, 0.0, -1.0], &opts).unwrap();
6712            }),
6713            ("closest_point", |s, _| {
6714                let opts = SceneClosestOptions::default();
6715                s.closest_point([0.5, 0.5, 1.0], &opts).unwrap();
6716            }),
6717            ("closest_point_on", |s, x| {
6718                let opts = SceneClosestOptions::default();
6719                s.closest_point_on(x.c, [0.5, 0.5, 1.0], &opts).unwrap();
6720            }),
6721            ("intersects", |s, x| {
6722                s.intersects(x.c, x.b).unwrap();
6723            }),
6724            ("mesh_distance", |s, x| {
6725                s.mesh_distance(x.c, x.b, 0.1).unwrap();
6726            }),
6727            ("triangles_near", |s, x| {
6728                let (mut ta, mut tb) = (Vec::new(), Vec::new());
6729                s.triangles_near(x.c, x.b, 0.1, &mut ta, &mut tb).unwrap();
6730            }),
6731            ("find_contacts", |s, _| {
6732                s.find_contacts(0.1, &ContactFilter::default(), &mut Vec::new())
6733                    .unwrap();
6734            }),
6735            ("add_section", |s, x| {
6736                let plane = Plane::new([0.0, 0.0, 0.1], [0.0, 0.0, 1.0]).unwrap();
6737                let desc = SectionDesc {
6738                    plane,
6739                    meshes: vec![x.c],
6740                    fill: true,
6741                };
6742                s.add_section(desc).unwrap();
6743            }),
6744            ("remove_section", |s, x| {
6745                s.remove_section(x.section).unwrap()
6746            }),
6747            ("set_section_plane", |s, x| {
6748                let plane = Plane::new([0.0, 0.0, 0.2], [0.0, 0.0, 1.0]).unwrap();
6749                s.set_section_plane(x.section, plane).unwrap();
6750            }),
6751            ("set_section_meshes", |s, x| {
6752                s.set_section_meshes(x.section, vec![x.a, x.c]).unwrap();
6753            }),
6754            ("set_section_fill", |s, x| {
6755                s.set_section_fill(x.section, true).unwrap();
6756            }),
6757        ];
6758        for (name, call) in calls {
6759            let (mut scene, ctx) = open_unit();
6760            assert!(scene.field_step_pending(), "{name}");
6761            call(&mut scene, &ctx);
6762            assert!(!scene.field_step_pending(), "{name} left the unit open");
6763        }
6764        // `update_sections` too (39 in all).
6765        let (mut scene, _) = open_unit();
6766        scene.update_sections().unwrap();
6767        assert!(!scene.field_step_pending(), "update_sections");
6768        // Reads and deferral leave the unit open.
6769        let (mut scene, x) = open_unit();
6770        scene.mesh(x.a).unwrap();
6771        scene.placement(x.b).unwrap();
6772        scene.is_current(x.a).unwrap();
6773        scene.field_values(x.field).unwrap();
6774        scene.field_status(x.field).unwrap();
6775        scene.colors(x.a).unwrap();
6776        assert_eq!(scene.field_step_meshes(), vec![x.a, x.b]);
6777        scene.set_defer_fields(false);
6778        scene.set_defer_fields(true);
6779        assert!(scene.field_step_pending(), "a read or deferral abandoned");
6780        assert_eq!(step_all(&mut scene, usize::MAX), 0);
6781        assert!(scene.field_status(x.field).unwrap().current);
6782    }
6783
6784    #[test]
6785    fn defer_leaves_fields_due() {
6786        let (mut scene, a, b, ids) = deferred_pair(0.15);
6787        assert_eq!(
6788            scene.field_step(FieldBudget::Points(1)),
6789            Ok(StepState::Idle)
6790        );
6791        counter::take();
6792        scene.set_placement(b, at(0.02, 0.0, 0.01)).unwrap();
6793        scene
6794            .edit(a, |tx| tx.set_positions(&[45], &[[0.5, 0.4, 0.05]]))
6795            .unwrap();
6796        assert_eq!(counter::take(), 0);
6797        for id in ids {
6798            assert!(!scene.field_status(id).unwrap().current);
6799        }
6800        assert!(!scene.field_step_pending());
6801        // Stepping brings both fields along in one unit, applied together.
6802        let before = seen_values(&scene, [a, b]);
6803        assert_eq!(
6804            scene.field_step(FieldBudget::Points(5)),
6805            Ok(StepState::Pending)
6806        );
6807        assert_eq!(seen_values(&scene, [a, b]), before);
6808        assert_eq!(counter::take(), 0);
6809        assert!(step_all(&mut scene, usize::MAX) == 0);
6810        assert!(counter::take() > 0);
6811        for id in ids {
6812            assert_matches(&mut scene, id);
6813        }
6814        // Without deferral the fields follow at once.
6815        scene.set_defer_fields(false);
6816        scene.set_placement(b, at(0.03, 0.0, 0.01)).unwrap();
6817        assert!(counter::take() > 0);
6818        for id in ids {
6819            assert!(scene.field_status(id).unwrap().current);
6820        }
6821        // Lazy fields are not stepped.
6822        scene.set_mode(a, UpdateMode::Lazy).unwrap();
6823        scene.set_mode(b, UpdateMode::Lazy).unwrap();
6824        scene.set_placement(b, at(0.04, 0.0, 0.01)).unwrap();
6825        assert_eq!(
6826            scene.field_step(FieldBudget::Points(1)),
6827            Ok(StepState::Idle)
6828        );
6829        assert!(!scene.field_status(ids[0]).unwrap().current);
6830    }
6831
6832    /// A contact group (spec §8): a sheet A, cubes B and C cutting it and
6833    /// each other, and a cube D cutting A only, with the pairs A-B, B-C,
6834    /// A-C and A-D. Each mesh has one field to its partners, registered in
6835    /// the order A, B, C, D.
6836    fn group_scene(mode: UpdateMode) -> (Scene, [MeshId; 4], [FieldId; 4]) {
6837        let mut scene = Scene::new(SceneOptions::default());
6838        let a = scene.add_mesh(sheet(10, 0.1), with_mode(mode)).unwrap();
6839        let b = scene
6840            .add_mesh(cube([0.1, 0.1, -0.2], 0.3), with_mode(mode))
6841            .unwrap();
6842        let c = scene
6843            .add_mesh(cube([0.3, 0.2, -0.1], 0.3), with_mode(mode))
6844            .unwrap();
6845        let d = scene
6846            .add_mesh(cube([0.75, 0.7, -0.1], 0.2), with_mode(mode))
6847            .unwrap();
6848        let mut add = |on: MeshId, to: Vec<MeshId>| {
6849            scene
6850                .add_distance_field(DistanceField {
6851                    to,
6852                    ..field("g", on, on, 0.12)
6853                })
6854                .unwrap()
6855        };
6856        let fields = [
6857            add(a, vec![b, c, d]),
6858            add(b, vec![a, c]),
6859            add(c, vec![a, b]),
6860            add(d, vec![a]),
6861        ];
6862        (scene, [a, b, c, d], fields)
6863    }
6864
6865    /// The fields applied by `f`, in order.
6866    fn applied(scene: &mut Scene, f: impl FnOnce(&mut Scene)) -> Vec<FieldId> {
6867        counter::take_applied();
6868        f(scene);
6869        counter::take_applied()
6870    }
6871
6872    #[test]
6873    fn group_recomputes_only_related_fields() {
6874        // Near searches never abandoned, so that a move recomputes only near
6875        // vertices.
6876        counter::set_near_limit(Some(usize::MAX));
6877        let (mut scene, [a, b, c, d], [fa, fb, fc, fd]) = group_scene(UpdateMode::Eager);
6878        // Every field touches something.
6879        for f in [fa, fb, fc, fd] {
6880            assert!(finite_count(&scene, f) > 0);
6881        }
6882        let a_slots = scene.mesh(a).unwrap().vertex_slots();
6883        counter::take();
6884        // D moves: only A's field (measured to D) and D's own.
6885        let got = applied(&mut scene, |s| {
6886            s.set_placement(d, at(0.01, 0.0, 0.0)).unwrap()
6887        });
6888        assert_eq!(got, [fa, fd]);
6889        // A's vertices near D, and D's: far fewer than A has.
6890        let recomputed = counter::take();
6891        assert!(recomputed > 0 && recomputed < a_slots, "{recomputed}");
6892        // A moves: every field.
6893        let got = applied(&mut scene, |s| {
6894            s.set_placement(a, at(0.0, 0.01, 0.0)).unwrap()
6895        });
6896        assert_eq!(got, [fa, fb, fc, fd]);
6897        // B moves, or is edited: A's, B's and C's, never D's.
6898        let got = applied(&mut scene, |s| {
6899            s.set_placement(b, at(0.0, 0.0, 0.01)).unwrap()
6900        });
6901        assert_eq!(got, [fa, fb, fc]);
6902        let got = applied(&mut scene, |s| {
6903            s.edit(b, |tx| tx.set_positions(&[0], &[[0.12, 0.1, -0.2]]))
6904                .unwrap();
6905        });
6906        assert_eq!(got, [fa, fb, fc]);
6907        // C moves: A's, B's and C's.
6908        let got = applied(&mut scene, |s| {
6909            s.set_placement(c, at(0.01, 0.0, 0.0)).unwrap()
6910        });
6911        assert_eq!(got, [fa, fb, fc]);
6912        // An edit of D: A's and D's.
6913        let got = applied(&mut scene, |s| {
6914            s.edit(d, |tx| tx.set_positions(&[0], &[[0.76, 0.7, -0.1]]))
6915                .unwrap();
6916        });
6917        assert_eq!(got, [fa, fd]);
6918        // Deferred, the same fields are due, and stepping applies them.
6919        scene.set_defer_fields(true);
6920        scene.set_placement(d, at(0.02, 0.0, 0.0)).unwrap();
6921        let current: Vec<bool> = [fa, fb, fc, fd]
6922            .iter()
6923            .map(|&f| scene.field_status(f).unwrap().current)
6924            .collect();
6925        assert_eq!(current, [false, true, true, false]);
6926        assert_eq!(
6927            scene.field_step(FieldBudget::Points(1)),
6928            Ok(StepState::Pending)
6929        );
6930        assert_eq!(scene.field_step_meshes(), [a, b, c, d,]);
6931        let got = applied(&mut scene, |s| {
6932            step_all(s, usize::MAX);
6933        });
6934        assert_eq!(got, [fa, fd]);
6935        counter::set_near_limit(None);
6936    }
6937
6938    /// The values a field's mesh would get from its targets one at a time:
6939    /// the minimum of single-target fields, ties to the lowest index.
6940    fn min_of_singles(scene: &mut Scene, id: FieldId) -> (Vec<u32>, Vec<u32>) {
6941        let desc = scene
6942            .fields
6943            .iter()
6944            .find(|f| f.id == id)
6945            .unwrap()
6946            .desc
6947            .clone();
6948        let singles: Vec<FieldId> = desc
6949            .to
6950            .iter()
6951            .enumerate()
6952            .map(|(k, &m)| {
6953                scene
6954                    .add_distance_field(DistanceField {
6955                        name: format!("single{k}"),
6956                        to: vec![m],
6957                        ..desc.clone()
6958                    })
6959                    .unwrap()
6960            })
6961            .collect();
6962        update_all(scene);
6963        let slots = scene.mesh(desc.on).unwrap().vertex_slots();
6964        let (mut values, mut nearest) = (Vec::new(), Vec::new());
6965        for v in 0..slots {
6966            let parts: Vec<f32> = singles
6967                .iter()
6968                .map(|&f| scene.field_values(f).unwrap()[v])
6969                .collect();
6970            let (mut best, mut at) = (parts[0], u32::MAX);
6971            if !best.is_nan() {
6972                best = f32::INFINITY;
6973                for (k, &x) in parts.iter().enumerate() {
6974                    if x < best {
6975                        (best, at) = (x, k as u32);
6976                    }
6977                }
6978            }
6979            values.push(best.to_bits());
6980            nearest.push(at);
6981        }
6982        for f in singles {
6983            scene.remove_field(f).unwrap();
6984        }
6985        (values, nearest)
6986    }
6987
6988    /// The field's value bits and nearest targets (`u32::MAX` past the end
6989    /// of the nearest list).
6990    fn shown(scene: &Scene, id: FieldId) -> (Vec<u32>, Vec<u32>) {
6991        let values = bits(scene.field_values(id).unwrap());
6992        let nearest = scene.field_nearest(id).unwrap();
6993        let nearest = (0..values.len())
6994            .map(|v| nearest.get(v).copied().unwrap_or(u32::MAX))
6995            .collect();
6996        (values, nearest)
6997    }
6998
6999    #[test]
7000    fn group_fields_match_single_targets() {
7001        let (mut scene, meshes, fields) = group_scene(UpdateMode::Eager);
7002        let mut rng = Lcg(0x6709);
7003        for step in 0..30 {
7004            let m = meshes[rng.below(4)];
7005            let t = [
7006                rng.range(-0.05, 0.05),
7007                rng.range(-0.05, 0.05),
7008                rng.range(-0.05, 0.05),
7009            ];
7010            scene
7011                .set_placement(m, turned(rng.range(-10.0, 10.0), t))
7012                .unwrap();
7013            for &f in &fields {
7014                let got = shown(&scene, f);
7015                assert_eq!(got, min_of_singles(&mut scene, f), "step {step}");
7016            }
7017        }
7018        // Every target was nearest somewhere in A's field.
7019        let (_, nearest) = shown(&scene, fields[0]);
7020        for k in 0..3 {
7021            assert!(nearest.contains(&k), "target {k}");
7022        }
7023    }
7024
7025    #[test]
7026    fn group_near_limit_matches_near_path() {
7027        // The same walk with every near search abandoned (limit 0) and with no
7028        // limit: the same values, nearest targets and reports.
7029        let mut runs = Vec::new();
7030        for limit in [Some(0), Some(usize::MAX)] {
7031            counter::set_near_limit(limit);
7032            counter::take_aborted();
7033            let (mut scene, meshes, fields) = group_scene(UpdateMode::Eager);
7034            let mut rng = Lcg(0x11);
7035            let mut out = Vec::new();
7036            for _ in 0..20 {
7037                let m = meshes[rng.below(4)];
7038                let t = [rng.range(-0.05, 0.05), rng.range(-0.05, 0.05), 0.0];
7039                scene
7040                    .set_placement(m, turned(rng.range(-5.0, 5.0), t))
7041                    .unwrap();
7042                out.push((fields.map(|f| shown(&scene, f)), scene.take_changes()));
7043            }
7044            runs.push((out, counter::take_aborted()));
7045        }
7046        counter::set_near_limit(None);
7047        assert!(runs[0].0 == runs[1].0);
7048        assert!(runs[0].1 > 0);
7049        assert_eq!(runs[1].1, 0);
7050    }
7051
7052    /// Everything a renderer sees of the group: every field's values and
7053    /// nearest targets, and the drained reports.
7054    type SeenGroup = (Vec<(Vec<u32>, Vec<u32>)>, Vec<crate::MeshChanges>);
7055
7056    fn seen_group(scene: &mut Scene, fields: &[FieldId; 4]) -> SeenGroup {
7057        let shown = fields.iter().map(|&f| shown(scene, f)).collect();
7058        (shown, scene.take_changes())
7059    }
7060
7061    #[test]
7062    fn group_stepped_matches_one_shot() {
7063        // Four copies of the group: three stepped with different budgets
7064        // (one abandoning units at random), the last updated in one shot.
7065        let budgets = [1, 23, usize::MAX];
7066        let mut twins: Vec<_> = (0..4)
7067            .map(|_| {
7068                let (mut scene, meshes, fields) = group_scene(UpdateMode::Eager);
7069                scene.set_defer_fields(true);
7070                scene.take_changes();
7071                (scene, meshes, fields)
7072            })
7073            .collect();
7074        let mut rng = Lcg(0x5e9);
7075        let mut abandons = Lcg(3);
7076        let mut spread = [0usize; 3];
7077        for step in 0..25 {
7078            let m = rng.below(4);
7079            let p = turned(
7080                rng.range(-8.0, 8.0),
7081                [
7082                    rng.range(-0.05, 0.05),
7083                    rng.range(-0.05, 0.05),
7084                    rng.range(-0.03, 0.03),
7085                ],
7086            );
7087            let edit = rng.below(3) == 0;
7088            let z = rng.range(-0.1, 0.1) as f32;
7089            let mut out = Vec::new();
7090            for (k, (scene, meshes, fields)) in twins.iter_mut().enumerate() {
7091                scene.set_placement(meshes[m], p).unwrap();
7092                if edit {
7093                    scene
7094                        .edit(meshes[0], |tx| tx.set_positions(&[12], &[[0.2, 0.1, z]]))
7095                        .unwrap();
7096                }
7097                match k {
7098                    0 => spread[0] += step_all(scene, budgets[0]),
7099                    1 => spread[1] += step_all_abandoning(scene, budgets[1], &mut abandons),
7100                    2 => spread[2] += step_all(scene, budgets[2]),
7101                    _ => update_all(scene),
7102                }
7103                out.push(seen_group(scene, fields));
7104            }
7105            for k in 0..3 {
7106                assert!(out[k] == out[3], "step {step}, twin {k}");
7107            }
7108        }
7109        assert!(spread[0] > 0 && spread[1] > 0, "{spread:?}");
7110        assert_eq!(spread[2], 0);
7111    }
7112
7113    #[test]
7114    fn removing_a_target_keeps_the_field() {
7115        let (mut scene, [a, b, c, d], [fa, fb, fc, fd]) = group_scene(UpdateMode::Eager);
7116        scene
7117            .set_coloring(
7118                a,
7119                Some(Coloring {
7120                    field: fa,
7121                    rule: contact_rule(0.05),
7122                }),
7123            )
7124            .unwrap();
7125        scene.take_changes();
7126        scene.remove_mesh(c).unwrap();
7127        // C's own field goes; A's and B's lose C and are recomputed at once.
7128        assert_eq!(scene.field_values(fc), Err(SceneError::UnknownField));
7129        assert_eq!(scene.field_targets(fa), Ok(&[b, d][..]));
7130        assert_eq!(scene.field_targets(fb), Ok(&[a][..]));
7131        assert_eq!(scene.field_target_status(fa).unwrap().len(), 2);
7132        for f in [fa, fb, fd] {
7133            assert!(scene.field_status(f).unwrap().current);
7134        }
7135        let got = shown(&scene, fa);
7136        assert_eq!(got, min_of_singles(&mut scene, fa));
7137        assert!(got.1.iter().all(|&n| n < 2 || n == u32::MAX));
7138        assert!(got.1.contains(&1));
7139        assert_colors(&scene, a, &contact_rule(0.05), fa);
7140        let ch = scene.take_changes();
7141        assert!(
7142            ch.iter()
7143                .find(|c| c.id == a)
7144                .unwrap()
7145                .fields_changed
7146                .contains(&fa)
7147        );
7148        // Deferred, the field waits for a step, then recomputes in full.
7149        scene.set_defer_fields(true);
7150        scene.remove_mesh(d).unwrap();
7151        assert_eq!(scene.field_targets(fa), Ok(&[b][..]));
7152        assert!(!scene.field_status(fa).unwrap().current);
7153        assert!(
7154            scene
7155                .field_nearest(fa)
7156                .unwrap()
7157                .iter()
7158                .all(|&n| n == 0 || n == u32::MAX)
7159        );
7160        counter::take();
7161        step_all(&mut scene, usize::MAX);
7162        assert_eq!(counter::take(), scene.mesh(a).unwrap().vertex_count());
7163        assert_eq!(shown(&scene, fa), min_of_singles(&mut scene, fa));
7164        // The last target goes: so does the field, and its colouring.
7165        scene.remove_mesh(b).unwrap();
7166        assert_eq!(scene.field_values(fa), Err(SceneError::UnknownField));
7167        assert_eq!(scene.field_values(fb), Err(SceneError::UnknownField));
7168        assert_eq!(scene.colors(a), Ok(None));
7169        assert!(scene.fields.is_empty());
7170    }
7171
7172    #[test]
7173    fn compact_remaps_nearest() {
7174        let (mut scene, [a, ..], [fa, ..]) = group_scene(UpdateMode::Eager);
7175        // A vertex near the cubes loses its triangles and goes.
7176        scene
7177            .edit(a, |tx| {
7178                let tris: Vec<u32> = (0..tx.mesh().triangle_slots() as u32)
7179                    .filter(|&t| tx.mesh().triangle(t).is_some_and(|tri| tri.contains(&0)))
7180                    .collect();
7181                tx.remove_triangles(&tris)?;
7182                tx.remove_vertices(&[0])
7183            })
7184            .unwrap();
7185        let before = scene.field_nearest(fa).unwrap().to_vec();
7186        assert!(before.iter().any(|&n| n != u32::MAX));
7187        scene.set_defer_fields(true);
7188        let remap = scene.compact(a).unwrap();
7189        let after = scene.field_nearest(fa).unwrap();
7190        assert_eq!(after.len(), scene.mesh(a).unwrap().vertex_slots());
7191        for (v, &n) in before.iter().enumerate() {
7192            if let Some(w) = remap.vertex(v as VertexId) {
7193                assert_eq!(after[w as usize], n, "vertex {v}");
7194            }
7195        }
7196        step_all(&mut scene, usize::MAX);
7197        assert_eq!(shown(&scene, fa), min_of_singles(&mut scene, fa));
7198    }
7199
7200    #[test]
7201    fn invalid_target_lists_rejected() {
7202        let (mut scene, [a, b, c, _], _) = group_scene(UpdateMode::Eager);
7203        let bad = |e| matches!(e, Err(SceneError::InvalidInput(_)));
7204        let desc = |to: Vec<MeshId>| DistanceField {
7205            to,
7206            ..field("x", a, b, 0.1)
7207        };
7208        assert!(bad(scene.add_distance_field(desc(vec![]))));
7209        assert!(bad(scene.add_distance_field(desc(vec![b, c, b]))));
7210        assert!(bad(scene.add_distance_field(desc(vec![b, a]))));
7211        assert_eq!(scene.fields.len(), 4);
7212        let id = scene.add_distance_field(desc(vec![c, b])).unwrap();
7213        assert_eq!(scene.field_targets(id), Ok(&[c, b][..]));
7214        // The single-target constructor is sugar for a list of one.
7215        let one = DistanceField::new("y", a, b, 0.1, SignMode::Auto);
7216        assert_eq!(one.to, [b]);
7217        scene.add_distance_field(one).unwrap();
7218    }
7219
7220    #[test]
7221    fn moving_one_target_spares_vertices_another_won() {
7222        // B is a sheet with a field to a small cube A through one corner and
7223        // a large cube C just under the rest. Moving A recomputes B's
7224        // vertices near A, or won by A, never the many that C won.
7225        counter::set_near_limit(Some(usize::MAX));
7226        let mut scene = Scene::new(SceneOptions::default());
7227        let b = scene
7228            .add_mesh(sheet(10, 0.1), MeshOptions::default())
7229            .unwrap();
7230        let a = scene
7231            .add_mesh(cube([0.05, 0.05, -0.1], 0.2), MeshOptions::default())
7232            .unwrap();
7233        let c = scene
7234            .add_mesh(cube([0.3, 0.3, -0.74], 0.7), MeshOptions::default())
7235            .unwrap();
7236        let fb = scene
7237            .add_distance_field(DistanceField {
7238                to: vec![a, c],
7239                ..field("d", b, b, 0.05)
7240            })
7241            .unwrap();
7242        let (_, nearest) = shown(&scene, fb);
7243        let by = |k: u32| nearest.iter().filter(|&&n| n == k).count();
7244        let (won_by_a, won_by_c) = (by(0), by(1));
7245        assert!(won_by_a > 0 && won_by_c > 0);
7246        counter::take_applied_counts();
7247        scene.set_placement(a, at(0.01, 0.0, 0.0)).unwrap();
7248        let counts = counter::take_applied_counts();
7249        assert_eq!(counts.len(), 1);
7250        let (f, recomputed) = counts[0];
7251        assert_eq!(f, fb);
7252        let slots = scene.mesh(b).unwrap().vertex_slots();
7253        assert!(recomputed > 0, "nothing recomputed");
7254        assert!(
7255            recomputed + won_by_c <= slots,
7256            "{recomputed} + {won_by_c} > {slots}"
7257        );
7258        // The values C won are untouched and still right.
7259        assert_eq!(shown(&scene, fb), min_of_singles(&mut scene, fb));
7260        counter::set_near_limit(None);
7261    }
7262
7263    #[test]
7264    fn status_aggregates_targets() {
7265        let (scene, [a, ..], [_, fb, fc, _]) = group_scene(UpdateMode::Eager);
7266        // B's field is to the open sheet A and the closed cube C.
7267        let each = scene.field_target_status(fb).unwrap();
7268        assert_eq!(each.len(), 2);
7269        assert!(!each[0].closed && each[1].closed);
7270        let all = scene.field_status(fb).unwrap().sign;
7271        assert!(!all.closed);
7272        assert_eq!(
7273            all.consistent_orientation,
7274            each.iter().all(|s| s.consistent_orientation)
7275        );
7276        assert_eq!(
7277            all.non_manifold_vertices,
7278            each.iter().map(|s| s.non_manifold_vertices).sum::<usize>()
7279        );
7280        assert_eq!(
7281            all.degenerate_triangles,
7282            each.iter().map(|s| s.degenerate_triangles).sum::<usize>()
7283        );
7284        assert_eq!(all.mode, each[0].mode);
7285        // C's field to [A, B]: the same, A first.
7286        assert!(!scene.field_status(fc).unwrap().sign.closed);
7287        let _ = a;
7288    }
7289
7290    #[test]
7291    fn group_walk_with_limits_steps_and_removal() {
7292        // The same random walk (placements of every mesh, edits of A, and
7293        // C removed half-way) under near limits 0 and none and step budgets
7294        // 1, 17 and all, against one-shot updates: the same values, nearest
7295        // targets and reports; and the minimum of single fields at the end.
7296        for seed in 0..4u64 {
7297            let mut runs = Vec::new();
7298            for limit in [Some(0), None] {
7299                for budget in [Some(1), Some(17), None] {
7300                    counter::set_near_limit(limit);
7301                    let (mut scene, meshes, fields) = group_scene(UpdateMode::Eager);
7302                    scene.set_defer_fields(budget.is_some());
7303                    scene.take_changes();
7304                    let mut rng = Lcg(seed);
7305                    let mut out = Vec::new();
7306                    for step in 0..16 {
7307                        if step == 8 {
7308                            scene.remove_mesh(meshes[2]).unwrap();
7309                        }
7310                        let k = [0, 1, 3, 0][rng.below(4)];
7311                        let t = [
7312                            rng.range(-0.04, 0.04),
7313                            rng.range(-0.04, 0.04),
7314                            rng.range(-0.03, 0.03),
7315                        ];
7316                        scene
7317                            .set_placement(meshes[k], turned(rng.range(-6.0, 6.0), t))
7318                            .unwrap();
7319                        if rng.below(3) == 0 {
7320                            let z = rng.range(-0.1, 0.1) as f32;
7321                            scene
7322                                .edit(meshes[0], |tx| tx.set_positions(&[23], &[[0.3, 0.2, z]]))
7323                                .unwrap();
7324                        }
7325                        if let Some(budget) = budget {
7326                            step_all(&mut scene, budget);
7327                        }
7328                        let live: Vec<FieldId> = fields
7329                            .iter()
7330                            .copied()
7331                            .filter(|&f| scene.field_targets(f).is_ok())
7332                            .collect();
7333                        let shownall: Vec<_> = live.iter().map(|&f| shown(&scene, f)).collect();
7334                        out.push((live, shownall, scene.take_changes()));
7335                    }
7336                    counter::set_near_limit(None);
7337                    for f in fields {
7338                        if scene.field_targets(f).is_ok() {
7339                            assert_eq!(
7340                                shown(&scene, f),
7341                                min_of_singles(&mut scene, f),
7342                                "seed {seed}"
7343                            );
7344                        }
7345                    }
7346                    runs.push(out);
7347                }
7348            }
7349            for (k, run) in runs.iter().enumerate().skip(1) {
7350                assert!(*run == runs[0], "seed {seed}, run {k}");
7351            }
7352        }
7353    }
7354
7355    /// A sheet A and a cube B, both Eager and deferring their fields, with
7356    /// nothing cached on them (no adjacency, no incidence) and no field.
7357    fn bare_pair(rng: &mut Lcg) -> (Scene, MeshId, MeshId) {
7358        let mut scene = Scene::new(SceneOptions::default());
7359        let a = scene
7360            .add_mesh(sheet(10, 0.1), MeshOptions::default())
7361            .unwrap();
7362        let b = scene
7363            .add_mesh(cube([0.3, 0.3, -0.2], 0.4), MeshOptions::default())
7364            .unwrap();
7365        scene.set_defer_fields(true);
7366        let p = turned(rng.range(-20.0, 20.0), [0.0, 0.0, rng.range(-0.1, 0.1)]);
7367        scene.set_placement(b, p).unwrap();
7368        scene.take_changes();
7369        (scene, a, b)
7370    }
7371
7372    /// Steps field `id` alone to the end with `Points(budget)`, abandoning
7373    /// at one in `abandon_one_in` step boundaries (0: never), and returns
7374    /// the steps that left it pending.
7375    fn step_field_all(
7376        scene: &mut Scene,
7377        id: FieldId,
7378        budget: usize,
7379        abandon_one_in: usize,
7380        rng: &mut Lcg,
7381    ) -> usize {
7382        let (mut pending, mut abandoned) = (0, 0);
7383        for _ in 0..1_000_000 {
7384            match scene
7385                .field_step_field(id, FieldBudget::Points(budget))
7386                .unwrap()
7387            {
7388                StepState::Idle => return pending,
7389                StepState::Pending => {
7390                    pending += 1;
7391                    // At most three times: an abandoned unit starts over.
7392                    if abandon_one_in > 0 && abandoned < 3 && rng.below(abandon_one_in) == 0 {
7393                        scene.abandon_field_step();
7394                        abandoned += 1;
7395                    }
7396                }
7397                StepState::Done => {}
7398            }
7399        }
7400        panic!("field_step_field never went idle");
7401    }
7402
7403    #[test]
7404    fn deferred_add_matches_eager_add() {
7405        // The same fields added at once and added deferred, then stepped
7406        // alone with small budgets (abandoned at random boundaries too):
7407        // the same values, colours and reports, bit for bit.
7408        let mut rng = Lcg(0xadd5);
7409        let mut spread = 0;
7410        for round in 0..12 {
7411            let state = rng.0;
7412            let md = random_max_distance(&mut rng);
7413            let sign = random_sign(&mut rng);
7414            let budget = [1, 13, 500, usize::MAX][round % 4];
7415            let abandon = if round % 3 == 0 { 4 } else { 0 };
7416            let mut out = Vec::new();
7417            for deferred in [false, true] {
7418                let mut r = Lcg(state);
7419                let (mut scene, a, b) = bare_pair(&mut r);
7420                let desc = DistanceField {
7421                    sign,
7422                    ..field("d", a, b, md)
7423                };
7424                let id = if deferred {
7425                    let id = scene.add_distance_field_deferred(desc).unwrap();
7426                    assert!(!scene.field_status(id).unwrap().current);
7427                    assert!(
7428                        scene
7429                            .take_changes()
7430                            .iter()
7431                            .all(|c| c.fields_changed.is_empty())
7432                    );
7433                    spread += step_field_all(&mut scene, id, budget, abandon, &mut r);
7434                    id
7435                } else {
7436                    scene.add_distance_field(desc).unwrap()
7437                };
7438                assert!(scene.field_status(id).unwrap().current, "round {round}");
7439                scene
7440                    .set_coloring(
7441                        a,
7442                        Some(Coloring {
7443                            field: id,
7444                            rule: distance_rule(0.2, 0.1),
7445                        }),
7446                    )
7447                    .unwrap();
7448                let status = scene.field_target_status(id).unwrap();
7449                let nearest = scene.field_nearest(id).unwrap().to_vec();
7450                out.push((seen(&mut scene, [a, b]), status, nearest));
7451            }
7452            assert_eq!(out[0], out[1], "round {round} budget {budget}");
7453        }
7454        assert!(spread > 100, "{spread}");
7455    }
7456
7457    #[test]
7458    fn deferred_add_of_several_targets_matches() {
7459        // A field measured to three meshes, added deferred and stepped one
7460        // point at a time, against the field added at once.
7461        let make = || {
7462            let (mut scene, [a, b, c, d], fields) = group_scene(UpdateMode::Eager);
7463            for f in fields {
7464                scene.remove_field(f).unwrap();
7465            }
7466            scene.set_defer_fields(true);
7467            scene.take_changes();
7468            (scene, [a, b, c, d])
7469        };
7470        let (mut eager, [a, b, c, d]) = make();
7471        let (mut stepped, _) = make();
7472        let desc = DistanceField {
7473            name: "multi".to_owned(),
7474            on: a,
7475            to: vec![b, c, d],
7476            max_distance: 0.3,
7477            sign: SignMode::Auto,
7478        };
7479        let fe = eager.add_distance_field(desc.clone()).unwrap();
7480        let fs = stepped.add_distance_field_deferred(desc).unwrap();
7481        let before = stepped.field_target_status(fs).unwrap();
7482        assert!(
7483            before
7484                .iter()
7485                .all(|s| !s.closed && s.mode == SignMode::Pseudonormal)
7486        );
7487        assert!(step_field_all(&mut stepped, fs, 1, 0, &mut Lcg(1)) > 50);
7488        assert_eq!(
7489            bits(eager.field_values(fe).unwrap()),
7490            bits(stepped.field_values(fs).unwrap())
7491        );
7492        assert_eq!(
7493            eager.field_nearest(fe).unwrap(),
7494            stepped.field_nearest(fs).unwrap()
7495        );
7496        assert_eq!(
7497            eager.field_target_status(fe).unwrap(),
7498            stepped.field_target_status(fs).unwrap()
7499        );
7500        assert_eq!(eager.take_changes(), stepped.take_changes());
7501    }
7502
7503    #[test]
7504    fn sign_data_builds_in_steps_and_resumes() {
7505        // After an edit of B, the field on A to B rebuilds B's sign data in
7506        // the unit's steps; a unit abandoned partway leaves the build to be
7507        // resumed, so the steps add up to those of an unbroken build.
7508        let (mut scene, a, b, [fa, fb]) = deferred_pair_signed(0.2, SignMode::Winding);
7509        let (mut twin, ..) = deferred_pair_signed(0.2, SignMode::Winding);
7510        let (mut probe, ..) = deferred_pair_signed(0.2, SignMode::Winding);
7511        let edit = |s: &mut Scene| {
7512            s.edit(b, |tx| tx.set_positions(&[0], &[[0.31, 0.29, -0.21]]))
7513                .unwrap();
7514        };
7515        edit(&mut scene);
7516        edit(&mut twin);
7517        edit(&mut probe);
7518        counter::take_sign_steps();
7519        assert!(step_all(&mut probe, 1) > 0);
7520        let unbroken = counter::take_sign_steps();
7521        assert!(unbroken >= 4, "{unbroken}");
7522        // Partway, then abandoned, then a placement change of A (sign data
7523        // of B stay valid), then on to the end.
7524        assert_eq!(
7525            scene.field_step(FieldBudget::Points(1)),
7526            Ok(StepState::Pending)
7527        );
7528        assert_eq!(scene.field_step_stage(), Some(Stage::Sign(0)));
7529        for _ in 0..unbroken / 2 {
7530            assert_eq!(
7531                scene.field_step(FieldBudget::Points(1)),
7532                Ok(StepState::Pending)
7533            );
7534        }
7535        scene.abandon_field_step();
7536        scene.set_placement(a, at(0.0, 0.0, 0.01)).unwrap();
7537        twin.set_placement(a, at(0.0, 0.0, 0.01)).unwrap();
7538        step_all(&mut scene, 1);
7539        let total = counter::take_sign_steps();
7540        twin.update_fields().unwrap();
7541        assert!(
7542            total >= unbroken && total <= unbroken + 2,
7543            "{total} against {unbroken}"
7544        );
7545        assert_eq!(seen(&mut scene, [a, b]), seen(&mut twin, [a, b]));
7546        // Partway, then an edit of B: the build starts over, and the result
7547        // still matches.
7548        edit(&mut scene);
7549        edit(&mut twin);
7550        for _ in 0..5 {
7551            assert_eq!(
7552                scene.field_step(FieldBudget::Points(1)),
7553                Ok(StepState::Pending)
7554            );
7555        }
7556        let moved = |s: &mut Scene| {
7557            s.edit(b, |tx| tx.set_positions(&[1], &[[0.7, 0.31, -0.2]]))
7558                .unwrap();
7559        };
7560        moved(&mut scene);
7561        moved(&mut twin);
7562        step_all(&mut scene, 3);
7563        twin.update_fields().unwrap();
7564        assert_eq!(seen(&mut scene, [a, b]), seen(&mut twin, [a, b]));
7565        assert!(scene.field_status(fa).unwrap().current);
7566        assert!(scene.field_status(fb).unwrap().current);
7567    }
7568
7569    #[test]
7570    fn near_search_runs_in_steps() {
7571        // A contact field on a large sheet: after a move, the near search
7572        // spreads over several one-point steps, and the result matches.
7573        let make = || {
7574            let mut scene = Scene::new(SceneOptions::default());
7575            let a = scene
7576                .add_mesh(sheet(120, 1.0 / 120.0), MeshOptions::default())
7577                .unwrap();
7578            let opts = MeshOptions {
7579                placement: at(0.0, 0.0, 0.01),
7580                ..MeshOptions::default()
7581            };
7582            let b = scene.add_mesh(sheet(120, 1.0 / 120.0), opts).unwrap();
7583            let f = scene.add_distance_field(field("d", a, b, 0.02)).unwrap();
7584            scene.set_defer_fields(true);
7585            scene
7586                .set_placement(b, turned(1.0, [0.0, 0.0, 0.012]))
7587                .unwrap();
7588            scene.take_changes();
7589            (scene, a, b, f)
7590        };
7591        counter::set_near_limit(Some(usize::MAX));
7592        let (mut scene, a, b, _) = make();
7593        let (mut twin, ..) = make();
7594        let mut near_steps = 0;
7595        for _ in 0..1_000_000 {
7596            if scene.field_step_stage() == Some(Stage::Near(0)) {
7597                near_steps += 1;
7598            }
7599            if scene.field_step(FieldBudget::Points(1)).unwrap() != StepState::Pending {
7600                break;
7601            }
7602        }
7603        twin.update_fields().unwrap();
7604        counter::set_near_limit(None);
7605        assert!(near_steps > 3, "{near_steps}");
7606        assert_eq!(seen(&mut scene, [a, b]), seen(&mut twin, [a, b]));
7607    }
7608
7609    #[test]
7610    fn withdraw_field_only_before_it_is_computed() {
7611        let (mut scene, a, b) = sheet_and_cube(UpdateMode::Eager);
7612        scene.take_changes();
7613        let f = scene
7614            .add_distance_field_deferred(field("d", a, b, 0.1))
7615            .unwrap();
7616        assert_eq!(scene.withdraw_field(f), Ok(true));
7617        assert_eq!(scene.field_values(f), Err(SceneError::UnknownField));
7618        assert!(
7619            scene.take_changes().is_empty(),
7620            "a withdrawn field reported"
7621        );
7622        assert_eq!(scene.withdraw_field(f), Err(SceneError::UnknownField));
7623        // The name is free again; once computed, withdrawing does nothing.
7624        let f = scene
7625            .add_distance_field_deferred(field("d", a, b, 0.1))
7626            .unwrap();
7627        scene
7628            .field_step_field(f, FieldBudget::Points(usize::MAX))
7629            .unwrap();
7630        assert_eq!(scene.withdraw_field(f), Ok(false));
7631        assert!(scene.field_status(f).unwrap().current);
7632        // A colouring by a field keeps it too.
7633        let g = scene
7634            .add_distance_field_deferred(field("g", a, b, 0.1))
7635            .unwrap();
7636        let rule = contact_rule(0.1);
7637        scene
7638            .set_coloring(a, Some(Coloring { field: g, rule }))
7639            .unwrap();
7640        assert_eq!(scene.withdraw_field(g), Ok(false));
7641        // An Eager add computes at once: nothing to withdraw.
7642        let h = scene.add_distance_field(field("h", a, b, 0.1)).unwrap();
7643        assert_eq!(scene.withdraw_field(h), Ok(false));
7644    }
7645
7646    #[test]
7647    fn field_step_field_steps_one_field() {
7648        let (mut scene, a, b, [fa, fb]) = deferred_pair(0.2);
7649        scene
7650            .set_placement(b, turned(5.0, [0.02, 0.0, 0.0]))
7651            .unwrap();
7652        scene.take_changes();
7653        // A unit over all due fields, then one over B's field alone.
7654        assert_eq!(
7655            scene.field_step(FieldBudget::Points(1)),
7656            Ok(StepState::Pending)
7657        );
7658        assert_eq!(
7659            scene.field_step_field(fb, FieldBudget::Points(1)),
7660            Ok(StepState::Pending)
7661        );
7662        assert_eq!(scene.field_step_meshes(), vec![b, a]);
7663        // `field_step` continues it.
7664        assert_eq!(
7665            scene.field_step(FieldBudget::Points(1)),
7666            Ok(StepState::Pending)
7667        );
7668        assert_eq!(scene.field_step_meshes(), vec![b, a]);
7669        while scene.field_step_field(fb, FieldBudget::Points(7)).unwrap() == StepState::Pending {}
7670        assert!(scene.field_status(fb).unwrap().current);
7671        assert!(!scene.field_status(fa).unwrap().current);
7672        assert_eq!(
7673            scene.field_step_field(fb, FieldBudget::Points(7)),
7674            Ok(StepState::Idle)
7675        );
7676        let changed: Vec<MeshId> = scene.take_changes().iter().map(|c| c.id).collect();
7677        assert!(!changed.contains(&a) || scene.colors(a).is_ok());
7678        assert_eq!(
7679            scene.field_step_field(FieldId(999), FieldBudget::Points(1)),
7680            Err(SceneError::UnknownField)
7681        );
7682    }
7683
7684    #[test]
7685    fn sign_build_survives_edit_and_undo_of_a_manual_target() {
7686        // A Manual target B: undo brings its revision back, its BVH is not
7687        // updated (it reads current again) and its slots stay grown. A
7688        // partial sign build of B must start over, not resume.
7689        let make = || {
7690            let mut scene = Scene::new(SceneOptions::default());
7691            let a = scene
7692                .add_mesh(sheet(10, 0.1), MeshOptions::default())
7693                .unwrap();
7694            let b = scene
7695                .add_mesh(sheet(30, 1.0 / 30.0), with_mode(UpdateMode::Manual))
7696                .unwrap();
7697            scene.set_placement(b, at(0.0, 0.0, 0.05)).unwrap();
7698            let f = scene
7699                .add_distance_field_deferred(field("d", a, b, 0.2))
7700                .unwrap();
7701            scene.take_changes();
7702            (scene, a, b, f)
7703        };
7704        let (mut scene, a, b, f) = make();
7705        let (mut twin, ..) = make();
7706        for _ in 0..3 {
7707            assert_eq!(
7708                scene.field_step_field(f, FieldBudget::Points(1)),
7709                Ok(StepState::Pending)
7710            );
7711        }
7712        assert_eq!(scene.field_step_stage(), Some(Stage::Sign(0)));
7713        let grow = |s: &mut Scene| {
7714            s.edit(b, |tx| {
7715                let vs = tx.add_vertices(&[[5.0, 5.0, 0.0], [6.0, 5.0, 0.0], [5.0, 6.0, 0.0]])?;
7716                tx.add_triangles(&[[vs[0], vs[1], vs[2]]])
7717            })
7718            .unwrap();
7719            s.undo().unwrap();
7720        };
7721        grow(&mut scene);
7722        grow(&mut twin);
7723        while scene.field_step_field(f, FieldBudget::Points(1)).unwrap() != StepState::Idle {}
7724        twin.update_fields().unwrap();
7725        assert_eq!(seen(&mut scene, [a, b]).0, seen(&mut twin, [a, b]).0);
7726        assert!(scene.field_status(f).unwrap().current);
7727        let g = twin.field_id(f.raw()).unwrap();
7728        assert_eq!(scene.field_target_status(f), twin.field_target_status(g));
7729    }
7730
7731    #[test]
7732    fn stale_partial_sign_builds_are_dropped() {
7733        // Review M3: an unfinished sign build kept with its field goes once
7734        // its mesh changes (at the next call that changes the scene), and
7735        // when the field is updated in one call or removed.
7736        let (mut scene, _, b, [fa, _]) = deferred_pair_signed(0.2, SignMode::Winding);
7737        let edit = |s: &mut Scene, z: f32| {
7738            s.edit(b, |tx| tx.set_positions(&[0], &[[0.31, 0.29, z]]))
7739                .unwrap();
7740        };
7741        let partial = |s: &mut Scene| {
7742            for _ in 0..2 {
7743                assert_eq!(s.field_step(FieldBudget::Points(1)), Ok(StepState::Pending));
7744            }
7745            s.abandon_field_step();
7746        };
7747        edit(&mut scene, -0.21);
7748        partial(&mut scene);
7749        assert_eq!(scene.kept_sign_builds(), 1);
7750        // The target changes: stale, dropped by the next scene change.
7751        edit(&mut scene, -0.22);
7752        scene.set_defer_fields(true);
7753        scene.take_changes();
7754        scene.abandon_field_step();
7755        scene.set_placement(b, at(0.0, 0.0, 0.0)).unwrap();
7756        assert_eq!(scene.kept_sign_builds(), 0);
7757        // Updated in one call: dropped.
7758        partial(&mut scene);
7759        assert_eq!(scene.kept_sign_builds(), 1);
7760        scene.update_fields().unwrap();
7761        assert_eq!(scene.kept_sign_builds(), 0);
7762        // Removed with its field.
7763        edit(&mut scene, -0.23);
7764        partial(&mut scene);
7765        assert_eq!(scene.kept_sign_builds(), 1);
7766        scene.remove_field(fa).unwrap();
7767        assert_eq!(scene.kept_sign_builds(), 0);
7768    }
7769
7770    #[test]
7771    fn stepped_unit_bit_identical_across_pools() {
7772        // Review M7: the same stepped units (a field build from scratch,
7773        // then a recompute after a move and one after an edit of the
7774        // target, abandoned once partway) on a one-thread and an
7775        // eight-thread pool, against the one-call update: bit for bit.
7776        use fast_mesh_store::par;
7777        let run = |threads: Option<usize>| {
7778            let go = || {
7779                let mut scene = Scene::new(SceneOptions::default());
7780                let a = scene
7781                    .add_mesh(sheet(90, 1.0 / 90.0), MeshOptions::default())
7782                    .unwrap();
7783                let opts = MeshOptions {
7784                    placement: at(0.0, 0.0, 0.04),
7785                    ..MeshOptions::default()
7786                };
7787                let b = scene.add_mesh(sheet(90, 1.0 / 90.0), opts).unwrap();
7788                scene.set_defer_fields(true);
7789                let f = scene
7790                    .add_distance_field_deferred(field("d", a, b, 0.1))
7791                    .unwrap();
7792                let one_call = threads.is_none();
7793                let settle = |s: &mut Scene| {
7794                    if one_call {
7795                        s.update_fields().unwrap();
7796                    } else {
7797                        for k in 0.. {
7798                            match s.field_step(FieldBudget::Points(97)).unwrap() {
7799                                StepState::Pending if k == 3 => s.abandon_field_step(),
7800                                StepState::Pending | StepState::Done => {}
7801                                _ => break,
7802                            }
7803                        }
7804                    }
7805                };
7806                if one_call {
7807                    scene.update_fields().unwrap();
7808                } else {
7809                    while scene.field_step_field(f, FieldBudget::Points(97)).unwrap()
7810                        != StepState::Idle
7811                    {}
7812                }
7813                let mut out = vec![seen(&mut scene, [a, b])];
7814                scene
7815                    .set_placement(b, turned(2.0, [0.01, 0.0, 0.035]))
7816                    .unwrap();
7817                settle(&mut scene);
7818                out.push(seen(&mut scene, [a, b]));
7819                scene
7820                    .edit(b, |tx| tx.set_positions(&[100], &[[0.5, 0.5, -0.01]]))
7821                    .unwrap();
7822                settle(&mut scene);
7823                out.push(seen(&mut scene, [a, b]));
7824                out
7825            };
7826            match threads {
7827                Some(k) => par::with_threads(k, go),
7828                None => {
7829                    par::set_force_sequential(true);
7830                    let out = go();
7831                    par::set_force_sequential(false);
7832                    out
7833                }
7834            }
7835        };
7836        let want = run(None);
7837        for k in [1, 8] {
7838            let got = run(Some(k));
7839            // Values and colours; reports may group differently around the
7840            // abandon, the values may not.
7841            for (g, w) in got.iter().zip(&want) {
7842                assert_eq!((&g.0, &g.1), (&w.0, &w.1), "{k} threads");
7843            }
7844        }
7845    }
7846
7847    /// A box `[min, min + size]`, the triangulation of [`cube`].
7848    fn bx(min: [f32; 3], size: [f32; 3]) -> Mesh {
7849        let mut pos = Vec::new();
7850        for i in 0..8u32 {
7851            let b = |bit: u32, s: f32| if i & bit != 0 { s } else { 0.0 };
7852            pos.extend_from_slice(&[
7853                min[0] + b(1, size[0]),
7854                min[1] + b(2, size[1]),
7855                min[2] + b(4, size[2]),
7856            ]);
7857        }
7858        let idx = [
7859            0, 2, 1, 1, 2, 3, 4, 5, 6, 5, 7, 6, 0, 1, 4, 1, 5, 4, 2, 6, 3, 3, 6, 7, 0, 4, 2, 2, 4,
7860            6, 1, 3, 5, 3, 7, 5,
7861        ];
7862        Mesh::from_buffers(&pos, &idx).unwrap()
7863    }
7864
7865    fn scaled_placement(translation: [f64; 3], scale: [f64; 3]) -> MeshOptions {
7866        MeshOptions {
7867            placement: Placement {
7868                rotation: Placement::IDENTITY.rotation,
7869                translation,
7870                scale,
7871            },
7872            ..MeshOptions::default()
7873        }
7874    }
7875
7876    /// A scene with A (a unit cube at `[3, 0, 0]`, Eager) and B (a unit
7877    /// cube), B placed by `place_b`.
7878    fn scaled_pair(place_b: MeshOptions) -> (Scene, MeshId, MeshId) {
7879        let mut scene = Scene::new(SceneOptions::default());
7880        let a = scene
7881            .add_mesh(cube([3.0, 0.0, 0.0], 1.0), MeshOptions::default())
7882            .unwrap();
7883        let b = scene.add_mesh(cube([0.0; 3], 1.0), place_b).unwrap();
7884        (scene, a, b)
7885    }
7886
7887    fn scaled_values(scene: &mut Scene, on: MeshId, to: &[MeshId]) -> (Vec<f32>, Vec<u32>) {
7888        let desc = DistanceField {
7889            name: "d".to_owned(),
7890            on,
7891            to: to.to_vec(),
7892            max_distance: f64::INFINITY,
7893            sign: SignMode::Auto,
7894        };
7895        let id = scene.add_distance_field(desc).unwrap();
7896        (
7897            scene.field_values(id).unwrap().to_vec(),
7898            scene.field_nearest(id).unwrap().to_vec(),
7899        )
7900    }
7901
7902    #[test]
7903    fn scaled_targets_match_rigid_twins() {
7904        // B scaled (uniform and non-uniform) measures like the rigid box of
7905        // the same world shape: A is the unit cube at x in [3, 4].
7906        for scale in [[2.0, 2.0, 2.0], [2.0, 1.0, 0.5], [0.5, 2.0, 1.5]] {
7907            let (mut scaled, a, b) = scaled_pair(scaled_placement([0.0; 3], scale));
7908            let (mut rigid, ra, rb) = scaled_pair(MeshOptions::default());
7909            // The rigid twin of B's world shape: [0, sx] x [0, sy] x [0, sz].
7910            let twin = rigid
7911                .add_mesh(
7912                    bx([0.0; 3], scale.map(|s| s as f32)),
7913                    MeshOptions::default(),
7914                )
7915                .unwrap();
7916            rigid.remove_mesh(rb).unwrap();
7917            let (got, got_near) = scaled_values(&mut scaled, a, &[b]);
7918            let (want, want_near) = scaled_values(&mut rigid, ra, &[twin]);
7919            assert_eq!(got.len(), want.len());
7920            assert_eq!(got_near, want_near, "scale {scale:?}");
7921            for (v, (g, w)) in got.iter().zip(&want).enumerate() {
7922                assert!(
7923                    close(*g, *w, f64::INFINITY),
7924                    "scale {scale:?} vertex {v}: got {g}, want {w}"
7925                );
7926            }
7927            // Every value is finite and positive: A is outside B.
7928            assert!(got.iter().all(|&x| x > 0.0), "scale {scale:?}: {got:?}");
7929            // Vertices on A's x = 3 face with y, z inside B's read the x gap.
7930            let ma = scaled.mesh(a).unwrap();
7931            for (v, &x) in got.iter().enumerate() {
7932                let p = ma.position(v as VertexId).unwrap();
7933                let w = scaled.local_to_world(a, p.map(f64::from)).unwrap();
7934                // Inside B's y, z span (with room): the value is the x gap.
7935                if (w[0] - 3.0).abs() < 1e-9 && w[2] < scale[2] - 0.25 {
7936                    assert!((x - (3.0 - scale[0]) as f32).abs() < 1e-4, "v{v}: {x}");
7937                }
7938            }
7939        }
7940    }
7941
7942    #[test]
7943    fn scaled_a_matches_rigid_twin() {
7944        // A scaled, B rigid: A's vertices carry the world distances. A's
7945        // world shape is [1.5, 3.5] x [0, 1]^2; the rigid twin has it
7946        // directly, with the same triangulation, so slot-wise.
7947        let scale = [2.0, 1.0, 1.0];
7948        let mut scaled = Scene::new(SceneOptions::default());
7949        let a = scaled
7950            .add_mesh(
7951                cube([0.0; 3], 1.0),
7952                scaled_placement([1.5, 0.0, 0.0], scale),
7953            )
7954            .unwrap();
7955        let b = scaled
7956            .add_mesh(cube([0.0; 3], 1.0), MeshOptions::default())
7957            .unwrap();
7958        let mut rigid = Scene::new(SceneOptions::default());
7959        let ra = rigid
7960            .add_mesh(bx([1.5, 0.0, 0.0], [2.0, 1.0, 1.0]), MeshOptions::default())
7961            .unwrap();
7962        let rb = rigid
7963            .add_mesh(cube([0.0; 3], 1.0), MeshOptions::default())
7964            .unwrap();
7965        let (got, got_near) = scaled_values(&mut scaled, a, &[b]);
7966        let (want, want_near) = scaled_values(&mut rigid, ra, &[rb]);
7967        assert_eq!(got_near, want_near);
7968        for (v, (g, w)) in got.iter().zip(&want).enumerate() {
7969            assert!(
7970                close(*g, *w, f64::INFINITY),
7971                "vertex {v}: got {g}, want {w}"
7972            );
7973        }
7974        // The x = 1.5 face reads the 0.5 gap to B.
7975        let ma = scaled.mesh(a).unwrap();
7976        for (v, &x) in got.iter().enumerate() {
7977            let p = ma.position(v as VertexId).unwrap();
7978            let w = scaled.local_to_world(a, p.map(f64::from)).unwrap();
7979            if (w[0] - 1.5).abs() < 1e-9 {
7980                assert!((x - 0.5).abs() < 1e-4, "v{v}: {x}");
7981            }
7982        }
7983    }
7984
7985    #[test]
7986    fn scaled_multi_target_and_stepped_updates_match() {
7987        // Two targets, one scaled: the minimum (ties to the lowest index)
7988        // matches the rigid twins'.
7989        let (mut scaled, a, b) = scaled_pair(scaled_placement([0.0; 3], [2.0, 1.0, 1.0]));
7990        let c = scaled
7991            .add_mesh(cube([3.0, 2.0, 0.0], 1.0), MeshOptions::default())
7992            .unwrap();
7993        let (mut rigid, ra, _) = scaled_pair(MeshOptions::default());
7994        let twin = rigid
7995            .add_mesh(bx([0.0; 3], [2.0, 1.0, 1.0]), MeshOptions::default())
7996            .unwrap();
7997        let rc = rigid
7998            .add_mesh(cube([3.0, 2.0, 0.0], 1.0), MeshOptions::default())
7999            .unwrap();
8000        let (got, got_near) = scaled_values(&mut scaled, a, &[b, c]);
8001        let (want, want_near) = scaled_values(&mut rigid, ra, &[twin, rc]);
8002        assert_eq!(got_near, want_near);
8003        for (v, (g, w)) in got.iter().zip(&want).enumerate() {
8004            assert!(
8005                close(*g, *w, f64::INFINITY),
8006                "vertex {v}: got {g}, want {w}"
8007            );
8008        }
8009        // Stepped: the field is registered, a placement makes it due while
8010        // deferred, then it is stepped to Done with the eager twin's values.
8011        let (mut eager, ea, eb) = scaled_pair(scaled_placement([0.0; 3], [2.0, 1.0, 0.5]));
8012        let (mut stepped, sa, sb) = scaled_pair(scaled_placement([0.0; 3], [2.0, 1.0, 0.5]));
8013        for scene in [&mut eager, &mut stepped] {
8014            scene.set_defer_fields(true);
8015        }
8016        let desc = DistanceField {
8017            name: "d".to_owned(),
8018            on: sa,
8019            to: vec![sb],
8020            max_distance: f64::INFINITY,
8021            sign: SignMode::Auto,
8022        };
8023        // Registering computes at once even while deferred; the placement
8024        // then leaves the field due for the steps.
8025        let id = stepped.add_distance_field(desc).unwrap();
8026        assert!(stepped.field_status(id).unwrap().current);
8027        let nudge = Placement {
8028            translation: [0.01, 0.0, 0.0],
8029            ..stepped.placement(sb).unwrap()
8030        };
8031        stepped.set_placement(sb, nudge).unwrap();
8032        assert!(!stepped.field_status(id).unwrap().current);
8033        loop {
8034            match stepped.field_step(FieldBudget::Points(3)).unwrap() {
8035                StepState::Done => break,
8036                StepState::Pending => {}
8037                StepState::Idle => panic!("a due field idles"),
8038            }
8039        }
8040        eager.set_placement(eb, nudge).unwrap();
8041        let want = scaled_values(&mut eager, ea, &[eb]).0;
8042        assert_eq!(bits(stepped.field_values(id).unwrap()), bits(&want));
8043    }
8044
8045    // ---- Stepped sign data refit, and the interaction tier (issue #173) ----
8046
8047    #[test]
8048    fn stepped_position_edit_refits_sign_data_like_a_rebuild() {
8049        // A position-only edit of B: the stepped update refits B's sign data
8050        // in one step instead of a whole build, with the same bits as an
8051        // Eager update (which refits) and as a new field (a whole build).
8052        let (mut scene, a, b, ids) = deferred_pair(f64::INFINITY);
8053        let (mut twin, ..) = deferred_pair(f64::INFINITY);
8054        twin.set_defer_fields(false);
8055        let edit = |s: &mut Scene, y: f32| {
8056            s.edit(b, |tx| tx.set_positions(&[0], &[[0.31, y, -0.21]]))
8057                .unwrap();
8058        };
8059        for y in [0.29, 0.27, 0.33] {
8060            edit(&mut scene, y);
8061            edit(&mut twin, y);
8062            counter::take_sign_steps();
8063            step_all(&mut scene, 1);
8064            assert_eq!(counter::take_sign_steps(), 0, "a refit, not a build");
8065            assert_eq!(seen(&mut scene, [a, b]), seen(&mut twin, [a, b]));
8066        }
8067        scene.set_defer_fields(false);
8068        let again = scene
8069            .add_distance_field(field("again", a, b, f64::INFINITY))
8070            .unwrap();
8071        assert_eq!(
8072            bits(scene.field_values(again).unwrap()),
8073            bits(scene.field_values(ids[0]).unwrap())
8074        );
8075        // A topology edit cannot refit: it builds, in steps.
8076        scene.set_defer_fields(true);
8077        scene.edit(b, |tx| tx.remove_triangles(&[0])).unwrap();
8078        counter::take_sign_steps();
8079        step_all(&mut scene, 1);
8080        assert!(counter::take_sign_steps() > 0);
8081    }
8082
8083    /// A pair of balls, A at the origin and B overlapping it, a field each
8084    /// way at `md`, both coloured.
8085    fn interacting_pair(md: f64) -> (Scene, MeshId, MeshId, [FieldId; 2]) {
8086        let mut scene = Scene::new(SceneOptions::default());
8087        let a = scene.add_mesh(ball(), MeshOptions::default()).unwrap();
8088        let b = scene.add_mesh(ball(), MeshOptions::default()).unwrap();
8089        scene.set_placement(b, at(1.2, 0.0, 0.0)).unwrap();
8090        let fa = scene.add_distance_field(field("d", a, b, md)).unwrap();
8091        let fb = scene.add_distance_field(field("d", b, a, md)).unwrap();
8092        for (m, f) in [(a, fa), (b, fb)] {
8093            let rule = distance_rule(0.5, 0.5);
8094            scene
8095                .set_coloring(m, Some(Coloring { field: f, rule }))
8096                .unwrap();
8097        }
8098        scene.take_changes();
8099        (scene, a, b, [fa, fb])
8100    }
8101
8102    #[test]
8103    fn interaction_recomputes_a_budget_then_settles_to_the_exact_field() {
8104        // Stepped and Eager updates, at an infinite bound and a wide one.
8105        for md in [f64::INFINITY, 10.0] {
8106            for stepped in [false, true] {
8107                let (mut scene, a, b, [fa, fb]) = interacting_pair(md);
8108                let slots = scene.field_values(fa).unwrap().len();
8109                if stepped {
8110                    scene.set_defer_fields(true);
8111                }
8112                scene.set_field_interaction(Some(slots / 8)).unwrap();
8113                assert_eq!(scene.field_interaction(), Some(slots / 8));
8114                for i in 1..=12 {
8115                    counter::take();
8116                    scene
8117                        .set_placement(b, at(1.2 + 0.01 * f64::from(i), 0.0, 0.0))
8118                        .unwrap();
8119                    if stepped {
8120                        step_all(&mut scene, 16);
8121                    }
8122                    let recomputed = counter::take();
8123                    // Two fields: each its near vertices and its sweep, well
8124                    // under the whole pair once the margin has settled.
8125                    if i > 6 {
8126                        assert!(recomputed < 2 * slots, "{recomputed} of {}", 2 * slots);
8127                    }
8128                    // Every vertex keeps a value: no far holes meanwhile.
8129                    for f in [fa, fb] {
8130                        assert!(scene.field_values(f).unwrap().iter().all(|x| x.is_finite()));
8131                    }
8132                }
8133                // The pointer stops: the exact field, bit for bit a new one.
8134                scene.set_field_interaction(None).unwrap();
8135                assert_eq!(scene.field_interaction(), None);
8136                if stepped {
8137                    assert!(!scene.field_status(fa).unwrap().current);
8138                    step_all(&mut scene, 16);
8139                    scene.set_defer_fields(false);
8140                }
8141                let exact_a = scene.add_distance_field(field("exact", a, b, md)).unwrap();
8142                let exact_b = scene.add_distance_field(field("exact", b, a, md)).unwrap();
8143                assert_eq!(
8144                    bits(scene.field_values(fa).unwrap()),
8145                    bits(scene.field_values(exact_a).unwrap())
8146                );
8147                assert_eq!(
8148                    bits(scene.field_values(fb).unwrap()),
8149                    bits(scene.field_values(exact_b).unwrap())
8150                );
8151                // And against an independent full computation.
8152                assert_matches(&mut scene, fa);
8153                assert_matches(&mut scene, fb);
8154                let rule = distance_rule(0.5, 0.5);
8155                assert_colors(&scene, a, &rule, fa);
8156                assert_colors(&scene, b, &rule, fb);
8157            }
8158        }
8159    }
8160
8161    #[test]
8162    fn interaction_without_a_change_costs_nothing_when_it_ends() {
8163        let (mut scene, _, b, [fa, fb]) = interacting_pair(f64::INFINITY);
8164        scene.set_field_interaction(Some(4)).unwrap();
8165        scene.set_field_interaction(None).unwrap();
8166        assert!(scene.field_status(fa).unwrap().current);
8167        assert!(scene.field_status(fb).unwrap().current);
8168        // A field of at most the budget stays exact during it.
8169        scene.set_field_interaction(Some(1 << 20)).unwrap();
8170        scene.set_placement(b, at(1.25, 0.0, 0.0)).unwrap();
8171        assert_matches(&mut scene, fa);
8172        scene.set_field_interaction(None).unwrap();
8173        assert!(scene.field_status(fa).unwrap().current);
8174    }
8175
8176    #[test]
8177    fn interaction_covers_an_edit_of_either_mesh_and_settles() {
8178        let (mut scene, a, b, [fa, fb]) = interacting_pair(f64::INFINITY);
8179        scene.set_field_interaction(Some(16)).unwrap();
8180        for (m, s) in [(a, 1.01f32), (b, 1.02), (a, 1.03)] {
8181            scene
8182                .edit(m, |tx| tx.set_positions(&[0], &[[s, 0.0, 0.0]]))
8183                .unwrap();
8184        }
8185        // During it, the sign data of the edited target were refitted.
8186        scene.set_field_interaction(None).unwrap();
8187        assert_matches(&mut scene, fa);
8188        assert_matches(&mut scene, fb);
8189    }
8190
8191    /// A dab near the top of a unit ball.
8192    fn ball_dab(strength: f32) -> crate::Dab {
8193        crate::Dab::Inflate(fast_mesh_ops::InflateOptions {
8194            brush: fast_mesh_ops::Brush {
8195                center: [0.0, 0.0, 1.0],
8196                radius: 0.8,
8197                falloff: fast_mesh_ops::Falloff::Smooth,
8198                strength,
8199            },
8200        })
8201    }
8202
8203    #[test]
8204    fn a_dab_marks_fields_due_and_a_stepped_update_refits() {
8205        // Scene::dab of a target and of the mesh a field is on, stepped as the
8206        // Worker steps it: due at once, refitted, with the bits of a new field.
8207        let (mut scene, a, b, [fa, fb]) = interacting_pair(f64::INFINITY);
8208        scene.set_defer_fields(true);
8209        for (m, s) in [(b, 0.02), (a, 0.03), (b, -0.01)] {
8210            assert!(scene.dab(m, &ball_dab(s)).unwrap().moved > 0);
8211            assert!(!scene.field_status(fa).unwrap().current);
8212            assert!(!scene.field_status(fb).unwrap().current);
8213            counter::take_sign_steps();
8214            step_all(&mut scene, 16);
8215            assert_eq!(counter::take_sign_steps(), 0, "a refit, not a build");
8216            assert!(scene.field_status(fa).unwrap().current);
8217            assert!(scene.field_status(fb).unwrap().current);
8218        }
8219        scene.set_defer_fields(false);
8220        let exact_a = scene
8221            .add_distance_field(field("exact", a, b, f64::INFINITY))
8222            .unwrap();
8223        let exact_b = scene
8224            .add_distance_field(field("exact", b, a, f64::INFINITY))
8225            .unwrap();
8226        assert_eq!(
8227            bits(scene.field_values(fa).unwrap()),
8228            bits(scene.field_values(exact_a).unwrap())
8229        );
8230        assert_eq!(
8231            bits(scene.field_values(fb).unwrap()),
8232            bits(scene.field_values(exact_b).unwrap())
8233        );
8234    }
8235
8236    #[test]
8237    fn dabs_during_an_interaction_settle_to_the_exact_field() {
8238        for stepped in [false, true] {
8239            let (mut scene, a, b, [fa, fb]) = interacting_pair(f64::INFINITY);
8240            let before = scene.field_values(fa).unwrap().to_vec();
8241            scene.set_defer_fields(stepped);
8242            scene.set_field_interaction(Some(4)).unwrap();
8243            for (m, s) in [(a, 0.02), (b, 0.03), (a, 0.02)] {
8244                scene.dab(m, &ball_dab(s)).unwrap();
8245                if stepped {
8246                    step_all(&mut scene, 16);
8247                }
8248            }
8249            assert_ne!(bits(scene.field_values(fa).unwrap()), bits(&before));
8250            scene.set_field_interaction(None).unwrap();
8251            if stepped {
8252                step_all(&mut scene, 16);
8253                scene.set_defer_fields(false);
8254            }
8255            assert_matches(&mut scene, fa);
8256            assert_matches(&mut scene, fb);
8257        }
8258    }
8259
8260    #[test]
8261    fn interaction_rejects_a_zero_budget() {
8262        let (mut scene, ..) = interacting_pair(f64::INFINITY);
8263        assert!(matches!(
8264            scene.set_field_interaction(Some(0)),
8265            Err(SceneError::InvalidInput(_))
8266        ));
8267        assert_eq!(scene.field_interaction(), None);
8268    }
8269
8270    #[test]
8271    fn interaction_is_bit_identical_for_any_thread_count() {
8272        let run = |threads: Option<usize>| {
8273            let go = || {
8274                let (mut scene, a, b, [fa, _]) = interacting_pair(f64::INFINITY);
8275                let slots = scene.field_values(fa).unwrap().len();
8276                scene.set_defer_fields(true);
8277                scene.set_field_interaction(Some(slots / 8)).unwrap();
8278                let mut out = Vec::new();
8279                for i in 1..=8 {
8280                    scene
8281                        .set_placement(b, at(1.2 + 0.015 * f64::from(i), 0.0, 0.0))
8282                        .unwrap();
8283                    step_all(&mut scene, 64);
8284                    out.push(seen_values(&scene, [a, b]));
8285                }
8286                scene.set_field_interaction(None).unwrap();
8287                step_all(&mut scene, 64);
8288                out.push(seen_values(&scene, [a, b]));
8289                out
8290            };
8291            match threads {
8292                Some(k) => par::with_threads(k, go),
8293                None => {
8294                    par::set_force_sequential(true);
8295                    let out = go();
8296                    par::set_force_sequential(false);
8297                    out
8298                }
8299            }
8300        };
8301        let want = run(None);
8302        for k in [1, 8] {
8303            assert_eq!(run(Some(k)), want, "{k} threads");
8304        }
8305    }
8306
8307    /// Whether the fields of a pair hold the bits of a new field, computed from
8308    /// scratch: the sign data they used then matched a full build.
8309    fn assert_like_a_new_field(scene: &mut Scene, id: FieldId, step: &str) {
8310        let (on, to, md, sign) = {
8311            let f = scene.fields.iter().find(|f| f.id == id).unwrap();
8312            (
8313                f.desc.on,
8314                f.desc.to.clone(),
8315                f.desc.max_distance,
8316                f.desc.sign,
8317            )
8318        };
8319        let defer = scene.defer_fields;
8320        scene.set_defer_fields(false);
8321        let fresh = scene
8322            .add_distance_field(DistanceField {
8323                name: "fresh".to_owned(),
8324                on,
8325                to,
8326                max_distance: md,
8327                sign,
8328            })
8329            .unwrap();
8330        let got = bits(scene.field_values(id).unwrap());
8331        let want = bits(scene.field_values(fresh).unwrap());
8332        scene.remove_field(fresh).unwrap();
8333        scene.set_defer_fields(defer);
8334        let off = got.iter().zip(&want).filter(|(g, w)| g != w).count();
8335        assert_eq!(off, 0, "{step}: {off} of {} values differ", got.len());
8336    }
8337
8338    /// Mirrors every vertex of `m` in z: the surface stays, its normals turn
8339    /// over, so that sign data that miss it read every sign wrong.
8340    fn mirror(scene: &mut Scene, m: MeshId) {
8341        let mesh = &scene.entry(m).unwrap().mesh;
8342        let ids: Vec<VertexId> = (0..mesh.vertex_slots() as VertexId).collect();
8343        let at: Vec<[f32; 3]> = ids
8344            .iter()
8345            .map(|&v| {
8346                let p = mesh.position(v).unwrap();
8347                [p[0], p[1], -p[2]]
8348            })
8349            .collect();
8350        scene.edit(m, |tx| tx.set_positions(&ids, &at)).unwrap();
8351    }
8352
8353    /// Runs what is due, stepped or at once.
8354    fn bring_up_to_date(scene: &mut Scene, stepped: bool) {
8355        if stepped {
8356            step_all(scene, 16);
8357        } else {
8358            scene.update_fields().unwrap();
8359        }
8360    }
8361
8362    #[test]
8363    fn refit_after_a_baked_target_is_a_rebuild() {
8364        // While a target is non-uniformly scaled its real sign data are unused
8365        // and its edits go unseen by them: back to rigid, they must not pass
8366        // for current after a refit that sees no moved vertices.
8367        for stepped in [false, true] {
8368            let (mut scene, _, b, [fa, _]) = interacting_pair(f64::INFINITY);
8369            scene.set_defer_fields(stepped);
8370            let rigid = at(1.2, 0.0, 0.0);
8371            let scaled = Placement {
8372                scale: [2.0, 1.0, 1.0],
8373                ..rigid
8374            };
8375            scene.set_placement(b, scaled).unwrap();
8376            bring_up_to_date(&mut scene, stepped);
8377            mirror(&mut scene, b);
8378            bring_up_to_date(&mut scene, stepped);
8379            scene.set_placement(b, rigid).unwrap();
8380            bring_up_to_date(&mut scene, stepped);
8381            assert_like_a_new_field(&mut scene, fa, "rigid again");
8382            // And it goes on refitting afterwards.
8383            scene
8384                .edit(b, |tx| tx.set_positions(&[0], &[[1.1, 0.0, 0.0]]))
8385                .unwrap();
8386            bring_up_to_date(&mut scene, stepped);
8387            assert_like_a_new_field(&mut scene, fa, "an edit after it");
8388        }
8389    }
8390
8391    #[test]
8392    fn sequences_of_edits_match_a_new_field() {
8393        // The differential test through the scene: edits, dabs, undo, redo,
8394        // rigid and scaled placements, interactions, abandoned units. After each
8395        // update the fields hold the bits of a new field.
8396        let mut rng = Lcg(0x5eed);
8397        for stepped in [false, true] {
8398            for round in 0..24 {
8399                let (mut scene, a, b, ids) = interacting_pair(f64::INFINITY);
8400                if round % 2 == 1 {
8401                    // Slivers in the target: a vertex on its neighbour.
8402                    let p = scene.entry(b).unwrap().mesh.position(7).unwrap();
8403                    scene.edit(b, |tx| tx.set_positions(&[6], &[p])).unwrap();
8404                }
8405                scene.set_defer_fields(stepped);
8406                let meshes = [a, b];
8407                let mut interacting = false;
8408                for op in 0..16 {
8409                    let r = (rng.next() * 1e9) as usize;
8410                    let m = meshes[(r >> 4) % 2];
8411                    let slots = scene.field_values(ids[0]).unwrap().len() as u32;
8412                    let v = (rng.next() * f64::from(slots)) as u32;
8413                    let what = r % 9;
8414                    let s = 0.8 + rng.next() as f32 / 2.0;
8415                    match what {
8416                        0..=2 => {
8417                            let _ = scene
8418                                .edit(m, |tx| tx.set_positions(&[v], &[[s, s - 1.0, 1.1 - s]]));
8419                        }
8420                        3 => {
8421                            scene.dab(m, &ball_dab(0.02)).unwrap();
8422                        }
8423                        8 => mirror(&mut scene, m),
8424                        4 => {
8425                            if scene.can_undo() {
8426                                scene.undo().unwrap();
8427                            }
8428                        }
8429                        5 => {
8430                            let scale = if r & 256 == 0 {
8431                                [1.0, 1.0, 1.0]
8432                            } else {
8433                                [1.5, 1.0, 1.0]
8434                            };
8435                            scene
8436                                .set_placement(
8437                                    b,
8438                                    Placement {
8439                                        scale,
8440                                        ..at(1.2, 0.0, f64::from(s) - 1.0)
8441                                    },
8442                                )
8443                                .unwrap();
8444                        }
8445                        6 => {
8446                            interacting = !interacting;
8447                            let points = interacting.then_some(4);
8448                            scene.set_field_interaction(points).unwrap();
8449                        }
8450                        _ => {
8451                            if stepped {
8452                                // A unit dropped partway.
8453                                let _ = scene.field_step(FieldBudget::Points(2));
8454                                scene.abandon_field_step();
8455                            }
8456                        }
8457                    }
8458                    bring_up_to_date(&mut scene, stepped);
8459                    if !interacting {
8460                        let step = format!("stepped {stepped}, round {round}, op {op} ({what})");
8461                        assert_like_a_new_field(&mut scene, ids[0], &step);
8462                        assert_like_a_new_field(&mut scene, ids[1], &step);
8463                    }
8464                }
8465            }
8466        }
8467    }
8468
8469    #[test]
8470    fn interaction_counts_the_motion_before_its_first_update() {
8471        // Apart, then the first update brings B against A: the vertex now in
8472        // contact held a large value, and only the motion says it can be close.
8473        let mut scene = Scene::new(SceneOptions::default());
8474        let a = scene.add_mesh(ball(), MeshOptions::default()).unwrap();
8475        let b = scene.add_mesh(ball(), MeshOptions::default()).unwrap();
8476        scene.set_placement(b, at(4.0, 0.0, 0.0)).unwrap();
8477        let fa = scene
8478            .add_distance_field(field("d", a, b, f64::INFINITY))
8479            .unwrap();
8480        let slots = scene.field_values(fa).unwrap().len();
8481        assert!(scene.field_values(fa).unwrap()[0] > 1.5);
8482        scene.fields[0].live.sweep = slots as u32 / 2;
8483        scene.set_field_interaction(Some(slots / 4)).unwrap();
8484        scene.set_placement(b, at(2.0, 0.0, 0.0)).unwrap();
8485        // Vertex 0 is (1, 0, 0), touching B's (-1, 0, 0) now.
8486        assert!(scene.field_values(fa).unwrap()[0].abs() < 1e-3);
8487        // A vertex left out is stamped with what it was computed at, so a
8488        // later update still counts this motion for it.
8489        let stamps = &scene.fields[0].live.stamps;
8490        let left = stamps.iter().filter(|&&t| t == 0.0).count();
8491        assert!(
8492            left > 0,
8493            "every vertex stamped as computed after the motion"
8494        );
8495    }
8496}