Skip to main content

loam_runtime/
field.rs

1use bytemuck::{Pod, Zeroable};
2
3use crate::domain::{DomainError, DomainSpace, Field, FieldKind, FieldProgram, Pose};
4use crate::entity::Entity;
5use crate::store::{Change, Cursor, Store};
6
7pub const OP_SPHERE: u32 = 1;
8pub const OP_BOX: u32 = 2;
9pub const OP_HALFSPACE: u32 = 3;
10pub const OP_HYPERSPHERE: u32 = 4;
11pub const OP_HALFSPACE4: u32 = 5;
12pub const OP_UNION: u32 = 6;
13pub const OP_INTERSECTION: u32 = 7;
14pub const OP_SUBTRACTION: u32 = 8;
15pub const OP_SMOOTH_UNION: u32 = 9;
16pub const OP_PUSH_POSE: u32 = 10;
17pub const OP_POP_POSE: u32 = 11;
18
19pub const MAX_STACK: usize = 32;
20pub const MAX_POSE_DEPTH: usize = 8;
21pub const MAX_PROGRAM_WORDS: usize = 1 << 20;
22
23pub const FIELD_FAR: f32 = 1e9;
24
25/// The minimum error a compiled program states when read through `DistanceField`.
26pub const FIELD_PROGRAM_ERROR: f32 = 1e-4;
27
28#[derive(Clone, Copy, Debug, PartialEq)]
29pub enum FieldOp {
30    Sphere { radius: f32 },
31    Box { half_extents: [f32; 3] },
32    HalfSpace { normal: [f32; 3], offset: f32 },
33    HyperSphere { radius: f32 },
34    HalfSpace4 { normal: [f32; 4], offset: f32 },
35    Union,
36    Intersection,
37    Subtraction,
38    SmoothUnion { radius: f32 },
39    Transform,
40}
41
42impl FieldOp {
43    pub fn operands(self) -> usize {
44        match self {
45            FieldOp::Sphere { .. }
46            | FieldOp::Box { .. }
47            | FieldOp::HalfSpace { .. }
48            | FieldOp::HyperSphere { .. }
49            | FieldOp::HalfSpace4 { .. } => 0,
50            FieldOp::Transform => 1,
51            FieldOp::Union
52            | FieldOp::Intersection
53            | FieldOp::Subtraction
54            | FieldOp::SmoothUnion { .. } => 2,
55        }
56    }
57
58    pub fn opcode(self) -> u32 {
59        match self {
60            FieldOp::Sphere { .. } => OP_SPHERE,
61            FieldOp::Box { .. } => OP_BOX,
62            FieldOp::HalfSpace { .. } => OP_HALFSPACE,
63            FieldOp::HyperSphere { .. } => OP_HYPERSPHERE,
64            FieldOp::HalfSpace4 { .. } => OP_HALFSPACE4,
65            FieldOp::Union => OP_UNION,
66            FieldOp::Intersection => OP_INTERSECTION,
67            FieldOp::Subtraction => OP_SUBTRACTION,
68            FieldOp::SmoothUnion { .. } => OP_SMOOTH_UNION,
69            FieldOp::Transform => OP_PUSH_POSE,
70        }
71    }
72
73    pub fn result_kind(self, operands: &[FieldKind]) -> FieldKind {
74        let weakest = operands
75            .iter()
76            .copied()
77            .fold(FieldKind::ExactDistance, FieldKind::weaker);
78        match self {
79            FieldOp::Union
80            | FieldOp::Intersection
81            | FieldOp::Subtraction
82            | FieldOp::SmoothUnion { .. } => weakest.weaker(FieldKind::ConservativeBound),
83            _ => weakest,
84        }
85    }
86}
87
88#[repr(C)]
89#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
90pub struct FieldPrimitive {
91    pub frame: [[f32; 4]; 4],
92    pub translation: [f32; 4],
93    pub params: [f32; 4],
94}
95
96#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
97pub struct FieldCounts {
98    pub primitive_evals: u64,
99    pub instructions: u64,
100    pub node_visits: u64,
101    pub node_skips: u64,
102}
103
104#[derive(Clone, Copy, Debug, PartialEq, Eq)]
105pub enum FieldError {
106    Opcode(u32),
107    Truncated,
108    StackOverflow,
109    StackUnderflow,
110    PoseDepth,
111    Primitive(u32),
112}
113
114#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
115pub struct FieldCost {
116    pub changed_inputs: u32,
117    pub affected_dependencies: u32,
118    pub program_layout: u32,
119    pub index_maintenance: u32,
120    pub full_rebuild: bool,
121}
122
123impl FieldCost {
124    pub fn is_idle(self) -> bool {
125        self == FieldCost::default()
126    }
127}
128
129// Moore, Kearfott, and Cloud 2009, Introduction to Interval Analysis, §2.1.
130#[derive(Clone, Copy)]
131struct FieldInterval {
132    lo: f64,
133    hi: f64,
134}
135
136impl FieldInterval {
137    const WHOLE: Self = Self {
138        lo: f64::NEG_INFINITY,
139        hi: f64::INFINITY,
140    };
141
142    fn exact(value: f32) -> Self {
143        if value.is_finite() {
144            Self {
145                lo: value as f64,
146                hi: value as f64,
147            }
148        } else {
149            Self::WHOLE
150        }
151    }
152
153    fn exact_f64(value: f64) -> Self {
154        if value.is_finite() {
155            Self {
156                lo: value,
157                hi: value,
158            }
159        } else {
160            Self::WHOLE
161        }
162    }
163
164    fn rounded(lo: f64, hi: f64) -> Self {
165        if lo.is_nan() || hi.is_nan() || lo > hi || !lo.is_finite() || !hi.is_finite() {
166            Self::WHOLE
167        } else {
168            Self {
169                lo: lo.next_down(),
170                hi: hi.next_up(),
171            }
172        }
173    }
174
175    fn add(self, other: Self) -> Self {
176        Self::rounded(self.lo + other.lo, self.hi + other.hi)
177    }
178
179    fn sub(self, other: Self) -> Self {
180        Self::rounded(self.lo - other.hi, self.hi - other.lo)
181    }
182
183    fn mul(self, other: Self) -> Self {
184        let products = [
185            self.lo * other.lo,
186            self.lo * other.hi,
187            self.hi * other.lo,
188            self.hi * other.hi,
189        ];
190        if products.iter().any(|value| value.is_nan()) {
191            return Self::WHOLE;
192        }
193        let lo = products.into_iter().fold(f64::INFINITY, f64::min);
194        let hi = products.into_iter().fold(f64::NEG_INFINITY, f64::max);
195        Self::rounded(lo, hi)
196    }
197
198    fn div(self, other: Self) -> Self {
199        if other.lo <= 0.0 && other.hi >= 0.0 {
200            return Self::WHOLE;
201        }
202        self.mul(Self::rounded(1.0 / other.hi, 1.0 / other.lo))
203    }
204
205    fn neg(self) -> Self {
206        Self {
207            lo: -self.hi,
208            hi: -self.lo,
209        }
210    }
211
212    fn abs(self) -> Self {
213        if self.lo >= 0.0 {
214            self
215        } else if self.hi <= 0.0 {
216            self.neg()
217        } else {
218            Self {
219                lo: 0.0,
220                hi: (-self.lo).max(self.hi),
221            }
222        }
223    }
224
225    fn min(self, other: Self) -> Self {
226        Self {
227            lo: self.lo.min(other.lo),
228            hi: self.hi.min(other.hi),
229        }
230    }
231
232    fn max(self, other: Self) -> Self {
233        Self {
234            lo: self.lo.max(other.lo),
235            hi: self.hi.max(other.hi),
236        }
237    }
238
239    fn clamp(self, lo: f64, hi: f64) -> Self {
240        Self {
241            lo: self.lo.clamp(lo, hi),
242            hi: self.hi.clamp(lo, hi),
243        }
244    }
245
246    fn sqrt(self) -> Self {
247        if self.hi < 0.0 {
248            return Self::WHOLE;
249        }
250        Self::rounded(self.lo.max(0.0).sqrt(), self.hi.sqrt())
251    }
252}
253
254#[derive(Clone, Copy)]
255struct FrameBounds {
256    deviation: f64,
257    singular_min: f64,
258    singular_max: f64,
259    inverse_max: f64,
260}
261
262#[derive(Clone, Copy)]
263struct MetricBounds {
264    min: f64,
265    max: f64,
266}
267
268impl MetricBounds {
269    const IDENTITY: Self = Self { min: 1.0, max: 1.0 };
270
271    fn through(self, frame: [[f32; 4]; 4]) -> Option<Self> {
272        let frame = frame_bounds(frame)?;
273        let min = FieldInterval::exact_f64(self.min)
274            .mul(FieldInterval::exact_f64(frame.singular_min))
275            .lo;
276        let max = FieldInterval::exact_f64(self.max)
277            .mul(FieldInterval::exact_f64(frame.singular_max))
278            .hi;
279        (min > 0.0 && min.is_finite() && max.is_finite()).then_some(Self { min, max })
280    }
281
282    fn scaled(self, scale: FieldInterval) -> Option<Self> {
283        let min = FieldInterval::exact_f64(self.min).mul(scale).lo;
284        let max = FieldInterval::exact_f64(self.max).mul(scale).hi;
285        (min > 0.0 && min.is_finite() && max.is_finite()).then_some(Self { min, max })
286    }
287}
288
289fn upper_f32(value: f64) -> f32 {
290    if value.is_nan() || value > f32::MAX as f64 {
291        return f32::INFINITY;
292    }
293    if value < -(f32::MAX as f64) {
294        return -f32::MAX;
295    }
296    let rounded = value as f32;
297    if rounded as f64 >= value {
298        rounded
299    } else {
300        rounded.next_up()
301    }
302}
303
304fn lower_f32(value: f64) -> f32 {
305    if value.is_nan() || value < -(f32::MAX as f64) {
306        return f32::NEG_INFINITY;
307    }
308    if value > f32::MAX as f64 {
309        return f32::MAX;
310    }
311    let rounded = value as f32;
312    if rounded as f64 <= value {
313        rounded
314    } else {
315        rounded.next_down()
316    }
317}
318
319#[repr(C)]
320#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
321pub struct FieldNode {
322    pub center: [f32; 4],
323    pub radius: f32,
324    pub scale: f32,
325    pub start: u32,
326    pub end: u32,
327    pub escape: u32,
328}
329
330impl FieldNode {
331    pub fn is_bounded(self) -> bool {
332        self.radius >= 0.0 && self.scale > 0.0
333    }
334
335    pub fn is_leaf(self) -> bool {
336        self.end != 0
337    }
338
339    pub fn lower_bound(self, point: [f32; 4]) -> f32 {
340        let mut sum = FieldInterval::exact(0.0);
341        for (at, center) in point.iter().zip(self.center) {
342            let d = FieldInterval::exact(*at).sub(FieldInterval::exact(center));
343            sum = sum.add(d.mul(d));
344        }
345        let gap = sum.sqrt().sub(FieldInterval::exact(self.radius));
346        if gap.lo <= 0.0 {
347            f32::NEG_INFINITY
348        } else {
349            lower_f32(FieldInterval::exact(self.scale).mul(gap).lo)
350        }
351    }
352}
353
354#[derive(Clone, Copy, Debug, PartialEq)]
355struct Ball {
356    center: [f32; 4],
357    radius: f32,
358    scale: f32,
359}
360
361const UNBOUNDED: Ball = Ball {
362    center: [0.0; 4],
363    radius: -1.0,
364    scale: 0.0,
365};
366
367impl Ball {
368    fn bounded(self) -> bool {
369        self.radius >= 0.0 && self.scale > 0.0
370    }
371
372    fn expanded(self, by: f32) -> Self {
373        if !self.bounded() {
374            return UNBOUNDED;
375        }
376        let expansion = FieldInterval::exact(by.abs()).div(FieldInterval::exact(self.scale));
377        let radius = upper_f32(FieldInterval::exact(self.radius).add(expansion).hi);
378        if !radius.is_finite() {
379            return UNBOUNDED;
380        }
381        Ball {
382            center: self.center,
383            radius,
384            scale: self.scale,
385        }
386    }
387
388    fn smaller(self, other: Self) -> Self {
389        match (self.bounded(), other.bounded()) {
390            (true, true) if other.radius < self.radius => other,
391            (true, _) => self,
392            (false, true) => other,
393            _ => UNBOUNDED,
394        }
395    }
396
397    fn enclosing(self, other: Self) -> Self {
398        if !self.bounded() || !other.bounded() {
399            return UNBOUNDED;
400        }
401        let mut center = [0.0; 4];
402        for (axis, component) in center.iter_mut().enumerate() {
403            *component = ((self.center[axis] as f64 + other.center[axis] as f64) * 0.5) as f32;
404        }
405        let radius_for = |ball: Self| {
406            ball.center
407                .into_iter()
408                .zip(center)
409                .fold(FieldInterval::exact(0.0), |sum, (from, to)| {
410                    let delta = FieldInterval::exact(from).sub(FieldInterval::exact(to));
411                    sum.add(delta.mul(delta))
412                })
413                .sqrt()
414                .add(FieldInterval::exact(ball.radius))
415        };
416        let radius = upper_f32(radius_for(self).max(radius_for(other)).hi);
417        if !radius.is_finite() || center.iter().any(|component| !component.is_finite()) {
418            UNBOUNDED
419        } else {
420            Ball {
421                center,
422                radius,
423                scale: self.scale.min(other.scale),
424            }
425        }
426    }
427
428    fn through(self, prim: &FieldPrimitive) -> Self {
429        let Some(frame) = frame_bounds(prim.frame) else {
430            return UNBOUNDED;
431        };
432        if !self.bounded() {
433            return UNBOUNDED;
434        }
435        let mut center = [0.0; 4];
436        let mut center_error_sq = FieldInterval::exact(0.0);
437        for (axis, component) in center.iter_mut().enumerate() {
438            let mut exact = FieldInterval::exact(prim.translation[axis]);
439            for (component, row) in self.center.into_iter().zip(prim.frame) {
440                exact =
441                    exact.add(FieldInterval::exact(row[axis]).mul(FieldInterval::exact(component)));
442            }
443            *component = ((exact.lo + exact.hi) * 0.5) as f32;
444            let error = FieldInterval::exact(*component).sub(exact).abs();
445            center_error_sq = center_error_sq.add(error.mul(error));
446        }
447        let center_norm = self
448            .center
449            .into_iter()
450            .fold(FieldInterval::exact(0.0), |sum, component| {
451                let component = FieldInterval::exact(component);
452                sum.add(component.mul(component))
453            })
454            .sqrt();
455        let inverse_norm = FieldInterval::exact_f64(frame.inverse_max);
456        let delta = FieldInterval::exact_f64(frame.deviation);
457        let radius = inverse_norm
458            .mul(FieldInterval::exact(self.radius))
459            .add(inverse_norm.mul(delta).mul(center_norm))
460            .add(center_error_sq.sqrt());
461        let radius = upper_f32(radius.hi);
462        let scale = lower_f32(
463            FieldInterval::exact(self.scale)
464                .mul(FieldInterval::exact_f64(frame.singular_min))
465                .lo,
466        );
467        if !radius.is_finite()
468            || !scale.is_finite()
469            || scale <= 0.0
470            || center.iter().any(|component| !component.is_finite())
471        {
472            UNBOUNDED
473        } else {
474            Ball {
475                center,
476                radius,
477                scale,
478            }
479        }
480    }
481}
482
483// Horn and Johnson 2013, Matrix Analysis, §§5.6, 7.3.
484fn frame_bounds(frame: [[f32; 4]; 4]) -> Option<FrameBounds> {
485    let mut delta = 0.0f64;
486    for (left_index, left_column) in frame.iter().enumerate() {
487        let mut row_sum = FieldInterval::exact(0.0);
488        for (right_index, right_column) in frame.iter().enumerate() {
489            let mut gram = FieldInterval::exact(0.0);
490            for (&left, &right) in left_column.iter().zip(right_column) {
491                gram = gram.add(FieldInterval::exact(left).mul(FieldInterval::exact(right)));
492            }
493            row_sum = row_sum.add(
494                FieldInterval::exact(if left_index == right_index { 1.0 } else { 0.0 })
495                    .sub(gram)
496                    .abs(),
497            );
498        }
499        delta = delta.max(row_sum.hi);
500    }
501    if !delta.is_finite() || delta >= 1.0 {
502        return None;
503    }
504    let remaining = FieldInterval::exact_f64(1.0)
505        .sub(FieldInterval::exact_f64(delta))
506        .sqrt();
507    let inverse_norm = FieldInterval::exact_f64(1.0).div(remaining).hi;
508    let singular_min = remaining.lo;
509    let singular_max = FieldInterval::exact_f64(1.0)
510        .add(FieldInterval::exact_f64(delta))
511        .sqrt()
512        .hi;
513    (inverse_norm.is_finite() && singular_min > 0.0 && singular_max.is_finite()).then_some(
514        FrameBounds {
515            deviation: delta,
516            singular_min,
517            singular_max,
518            inverse_max: inverse_norm,
519        },
520    )
521}
522
523fn primitive_ball(op: u32, prim: &FieldPrimitive, extruded_w: bool) -> Ball {
524    let r = prim.params;
525    let local = match op {
526        OP_HYPERSPHERE => Ball {
527            center: [0.0; 4],
528            radius: r[0].abs(),
529            scale: 1.0,
530        },
531        OP_SPHERE if !extruded_w => Ball {
532            center: [0.0; 4],
533            radius: r[0].abs(),
534            scale: 1.0,
535        },
536        OP_BOX if !extruded_w => Ball {
537            center: [0.0; 4],
538            radius: upper_f32(
539                r[..3]
540                    .iter()
541                    .fold(FieldInterval::exact(0.0), |sum, &component| {
542                        let component = FieldInterval::exact(component);
543                        sum.add(component.mul(component))
544                    })
545                    .sqrt()
546                    .hi,
547            ),
548            scale: 1.0,
549        },
550        _ => UNBOUNDED,
551    };
552    local.through(prim)
553}
554
555fn subtree_ball(primitives: &[FieldPrimitive], range: &[u32], extruded_w: bool) -> Ball {
556    if !range.len().is_multiple_of(2) {
557        return UNBOUNDED;
558    }
559    let mut stack = [UNBOUNDED; MAX_STACK];
560    let mut poses = [0u32; MAX_POSE_DEPTH];
561    let mut sp = 0usize;
562    let mut pp = 0usize;
563    for word in range.chunks_exact(2) {
564        let (op, arg) = (word[0], word[1]);
565        match op {
566            OP_SPHERE | OP_BOX | OP_HALFSPACE | OP_HYPERSPHERE | OP_HALFSPACE4 => {
567                let Some(prim) = primitives.get(arg as usize) else {
568                    return UNBOUNDED;
569                };
570                if sp == MAX_STACK {
571                    return UNBOUNDED;
572                }
573                stack[sp] = primitive_ball(op, prim, extruded_w);
574                sp += 1;
575            }
576            OP_UNION | OP_INTERSECTION | OP_SUBTRACTION | OP_SMOOTH_UNION => {
577                if sp < 2 {
578                    return UNBOUNDED;
579                }
580                let b = stack[sp - 1];
581                let a = stack[sp - 2];
582                sp -= 1;
583                stack[sp - 1] = match op {
584                    OP_UNION => a.enclosing(b),
585                    OP_INTERSECTION => a.smaller(b),
586                    OP_SUBTRACTION => a,
587                    _ => a.enclosing(b).expanded(f32::from_bits(arg)),
588                };
589            }
590            OP_PUSH_POSE => {
591                if pp == MAX_POSE_DEPTH {
592                    return UNBOUNDED;
593                }
594                poses[pp] = arg;
595                pp += 1;
596            }
597            OP_POP_POSE => {
598                if pp == 0 || sp == 0 {
599                    return UNBOUNDED;
600                }
601                pp -= 1;
602                let Some(prim) = primitives.get(poses[pp] as usize) else {
603                    return UNBOUNDED;
604                };
605                stack[sp - 1] = stack[sp - 1].through(prim);
606            }
607            _ => return UNBOUNDED,
608        }
609    }
610    if sp == 1 {
611        stack[0]
612    } else {
613        UNBOUNDED
614    }
615}
616
617#[derive(Clone, Copy, Debug)]
618struct Cut {
619    node: u32,
620    start: u32,
621    end: u32,
622    implicit: bool,
623    ball: Ball,
624}
625
626fn widest_axis(leaves: &[Cut]) -> usize {
627    let mut widest = 0;
628    let mut spread = -1.0f32;
629    for axis in 0..4 {
630        let mut lo = f32::INFINITY;
631        let mut hi = f32::NEG_INFINITY;
632        for leaf in leaves {
633            lo = lo.min(leaf.ball.center[axis]);
634            hi = hi.max(leaf.ball.center[axis]);
635        }
636        if hi - lo > spread {
637            spread = hi - lo;
638            widest = axis;
639        }
640    }
641    widest
642}
643
644fn build_tree(leaves: &mut [Cut], out: &mut Vec<FieldNode>) {
645    if leaves.len() == 1 {
646        let leaf = leaves[0];
647        out.push(FieldNode {
648            center: leaf.ball.center,
649            radius: leaf.ball.radius,
650            scale: leaf.ball.scale,
651            start: leaf.start,
652            end: leaf.end,
653            escape: 0,
654        });
655        let at = out.len() - 1;
656        out[at].escape = out.len() as u32;
657        return;
658    }
659    let at = out.len();
660    out.push(FieldNode::default());
661    let axis = widest_axis(leaves);
662    let mid = leaves.len() / 2;
663    leaves.select_nth_unstable_by(mid, |a, b| {
664        a.ball.center[axis]
665            .total_cmp(&b.ball.center[axis])
666            .then(a.start.cmp(&b.start))
667    });
668    let (left, right) = leaves.split_at_mut(mid);
669    build_tree(left, out);
670    let right_at = out[at + 1].escape as usize;
671    build_tree(right, out);
672    let ball = ball_of(out[at + 1]).enclosing(ball_of(out[right_at]));
673    out[at] = FieldNode {
674        center: ball.center,
675        radius: ball.radius,
676        scale: ball.scale,
677        start: 0,
678        end: 0,
679        escape: out.len() as u32,
680    };
681}
682
683fn ball_of(node: FieldNode) -> Ball {
684    Ball {
685        center: node.center,
686        radius: node.radius,
687        scale: node.scale,
688    }
689}
690
691fn local(prim: &FieldPrimitive, p: [f32; 4]) -> [f32; 4] {
692    let d = [
693        p[0] - prim.translation[0],
694        p[1] - prim.translation[1],
695        p[2] - prim.translation[2],
696        p[3] - prim.translation[3],
697    ];
698    let mut out = [0.0; 4];
699    for (slot, column) in out.iter_mut().zip(prim.frame) {
700        *slot = column[0] * d[0] + column[1] * d[1] + column[2] * d[2] + column[3] * d[3];
701    }
702    out
703}
704
705fn local_interval(prim: &FieldPrimitive, p: [FieldInterval; 4]) -> [FieldInterval; 4] {
706    let d = [
707        p[0].sub(FieldInterval::exact(prim.translation[0])),
708        p[1].sub(FieldInterval::exact(prim.translation[1])),
709        p[2].sub(FieldInterval::exact(prim.translation[2])),
710        p[3].sub(FieldInterval::exact(prim.translation[3])),
711    ];
712    let mut out = [FieldInterval::exact(0.0); 4];
713    for (slot, column) in out.iter_mut().zip(prim.frame) {
714        *slot = FieldInterval::exact(column[0])
715            .mul(d[0])
716            .add(FieldInterval::exact(column[1]).mul(d[1]))
717            .add(FieldInterval::exact(column[2]).mul(d[2]))
718            .add(FieldInterval::exact(column[3]).mul(d[3]));
719    }
720    out
721}
722
723fn primitive_distance(op: u32, prim: &FieldPrimitive, p: [f32; 4]) -> f32 {
724    let q = local(prim, p);
725    let r = prim.params;
726    match op {
727        OP_SPHERE => (q[0] * q[0] + q[1] * q[1] + q[2] * q[2]).sqrt() - r[0],
728        OP_BOX => {
729            let d = [q[0].abs() - r[0], q[1].abs() - r[1], q[2].abs() - r[2]];
730            let outside =
731                (d[0].max(0.0).powi(2) + d[1].max(0.0).powi(2) + d[2].max(0.0).powi(2)).sqrt();
732            outside + d[0].max(d[1]).max(d[2]).min(0.0)
733        }
734        OP_HALFSPACE => q[0] * r[0] + q[1] * r[1] + q[2] * r[2],
735        OP_HYPERSPHERE => (q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]).sqrt() - r[0],
736        OP_HALFSPACE4 => q[0] * r[0] + q[1] * r[1] + q[2] * r[2] + q[3] * r[3],
737        _ => FIELD_FAR,
738    }
739}
740
741fn primitive_interval(op: u32, prim: &FieldPrimitive, p: [FieldInterval; 4]) -> FieldInterval {
742    let q = local_interval(prim, p);
743    let r = prim.params.map(FieldInterval::exact);
744    match op {
745        OP_SPHERE => q[..3]
746            .iter()
747            .fold(FieldInterval::exact(0.0), |sum, &component| {
748                sum.add(component.mul(component))
749            })
750            .sqrt()
751            .sub(r[0]),
752        OP_BOX => {
753            let d = [
754                q[0].abs().sub(r[0]),
755                q[1].abs().sub(r[1]),
756                q[2].abs().sub(r[2]),
757            ];
758            let zero = FieldInterval::exact(0.0);
759            let outside = d
760                .iter()
761                .fold(zero, |sum, &component| {
762                    let positive = component.max(zero);
763                    sum.add(positive.mul(positive))
764                })
765                .sqrt();
766            outside.add(d[0].max(d[1]).max(d[2]).min(zero))
767        }
768        OP_HALFSPACE => q[0].mul(r[0]).add(q[1].mul(r[1])).add(q[2].mul(r[2])),
769        OP_HYPERSPHERE => q
770            .iter()
771            .fold(FieldInterval::exact(0.0), |sum, &component| {
772                sum.add(component.mul(component))
773            })
774            .sqrt()
775            .sub(r[0]),
776        OP_HALFSPACE4 => q[0]
777            .mul(r[0])
778            .add(q[1].mul(r[1]))
779            .add(q[2].mul(r[2]))
780            .add(q[3].mul(r[3])),
781        _ => FieldInterval::exact(FIELD_FAR),
782    }
783}
784
785fn primitive_metric_interval(
786    op: u32,
787    prim: &FieldPrimitive,
788    p: [FieldInterval; 4],
789    metric: MetricBounds,
790) -> FieldInterval {
791    let value = primitive_interval(op, prim, p);
792    let Some(mut metric) = metric.through(prim.frame) else {
793        return FieldInterval::WHOLE;
794    };
795    if matches!(op, OP_HALFSPACE | OP_HALFSPACE4) {
796        let dimensions = if op == OP_HALFSPACE { 3 } else { 4 };
797        let normal = prim.params[..dimensions]
798            .iter()
799            .fold(FieldInterval::exact(0.0), |sum, &component| {
800                let component = FieldInterval::exact(component);
801                sum.add(component.mul(component))
802            })
803            .sqrt();
804        let Some(scaled) = metric.scaled(normal) else {
805            return FieldInterval::WHOLE;
806        };
807        metric = scaled;
808    }
809    value.div(FieldInterval {
810        lo: metric.min,
811        hi: metric.max,
812    })
813}
814
815// Quilez, "Smooth minimum", iquilezles.org/articles/smin, polynomial form.
816fn smooth_min(a: f32, b: f32, k: f32) -> f32 {
817    let h = (0.5 + 0.5 * (b - a) / k).clamp(0.0, 1.0);
818    (b * (1.0 - h) + a * h) - k * h * (1.0 - h)
819}
820
821fn smooth_min_interval(a: FieldInterval, b: FieldInterval, k: FieldInterval) -> FieldInterval {
822    let one = FieldInterval::exact(1.0);
823    let half = FieldInterval::exact(0.5);
824    let h = half.add(half.mul(b.sub(a)).div(k)).clamp(0.0, 1.0);
825    b.mul(one.sub(h))
826        .add(a.mul(h))
827        .sub(k.mul(h).mul(one.sub(h)))
828}
829
830fn run<const COUNT: bool>(
831    program: &[u32],
832    primitives: &[FieldPrimitive],
833    point: [f32; 4],
834    counts: &mut FieldCounts,
835) -> Result<f32, FieldError> {
836    if program.is_empty() {
837        return Ok(FIELD_FAR);
838    }
839    if !program.len().is_multiple_of(2) {
840        return Err(FieldError::Truncated);
841    }
842    let mut stack = [0.0f32; MAX_STACK];
843    let mut points = [[0.0f32; 4]; MAX_POSE_DEPTH];
844    let mut sp = 0usize;
845    let mut pp = 0usize;
846    let mut p = point;
847    for word in program.chunks_exact(2) {
848        let (op, arg) = (word[0], word[1]);
849        if COUNT {
850            counts.instructions += 1;
851        }
852        match op {
853            OP_SPHERE | OP_BOX | OP_HALFSPACE | OP_HYPERSPHERE | OP_HALFSPACE4 => {
854                let prim = primitives
855                    .get(arg as usize)
856                    .ok_or(FieldError::Primitive(arg))?;
857                if COUNT {
858                    counts.primitive_evals += 1;
859                }
860                if sp == MAX_STACK {
861                    return Err(FieldError::StackOverflow);
862                }
863                stack[sp] = primitive_distance(op, prim, p);
864                sp += 1;
865            }
866            OP_UNION | OP_INTERSECTION | OP_SUBTRACTION | OP_SMOOTH_UNION => {
867                if sp < 2 {
868                    return Err(FieldError::StackUnderflow);
869                }
870                let b = stack[sp - 1];
871                let a = stack[sp - 2];
872                sp -= 1;
873                stack[sp - 1] = match op {
874                    OP_UNION => a.min(b),
875                    OP_INTERSECTION => a.max(b),
876                    OP_SUBTRACTION => a.max(-b),
877                    _ => smooth_min(a, b, f32::from_bits(arg)),
878                };
879            }
880            OP_PUSH_POSE => {
881                let prim = primitives
882                    .get(arg as usize)
883                    .ok_or(FieldError::Primitive(arg))?;
884                if pp == MAX_POSE_DEPTH {
885                    return Err(FieldError::PoseDepth);
886                }
887                points[pp] = p;
888                pp += 1;
889                p = local(prim, p);
890            }
891            OP_POP_POSE => {
892                if pp == 0 {
893                    return Err(FieldError::PoseDepth);
894                }
895                pp -= 1;
896                p = points[pp];
897            }
898            other => return Err(FieldError::Opcode(other)),
899        }
900    }
901    if sp != 1 {
902        return Err(FieldError::StackUnderflow);
903    }
904    Ok(stack[0])
905}
906
907fn run_interval(
908    program: &[u32],
909    primitives: &[FieldPrimitive],
910    point: [f32; 4],
911    exact_distance: bool,
912) -> Result<FieldInterval, FieldError> {
913    if program.is_empty() {
914        return Ok(FieldInterval::exact(FIELD_FAR));
915    }
916    if !program.len().is_multiple_of(2) {
917        return Err(FieldError::Truncated);
918    }
919    let mut stack = [FieldInterval::exact(0.0); MAX_STACK];
920    let mut points = [[FieldInterval::exact(0.0); 4]; MAX_POSE_DEPTH];
921    let mut metrics = [Some(MetricBounds::IDENTITY); MAX_POSE_DEPTH];
922    let mut sp = 0usize;
923    let mut pp = 0usize;
924    let mut p = point.map(FieldInterval::exact);
925    let mut metric = Some(MetricBounds::IDENTITY);
926    for word in program.chunks_exact(2) {
927        let (op, arg) = (word[0], word[1]);
928        match op {
929            OP_SPHERE | OP_BOX | OP_HALFSPACE | OP_HYPERSPHERE | OP_HALFSPACE4 => {
930                let prim = primitives
931                    .get(arg as usize)
932                    .ok_or(FieldError::Primitive(arg))?;
933                if sp == MAX_STACK {
934                    return Err(FieldError::StackOverflow);
935                }
936                stack[sp] = if exact_distance {
937                    match metric {
938                        Some(metric) => primitive_metric_interval(op, prim, p, metric),
939                        None => FieldInterval::WHOLE,
940                    }
941                } else {
942                    primitive_interval(op, prim, p)
943                };
944                sp += 1;
945            }
946            OP_UNION | OP_INTERSECTION | OP_SUBTRACTION | OP_SMOOTH_UNION => {
947                if sp < 2 {
948                    return Err(FieldError::StackUnderflow);
949                }
950                let b = stack[sp - 1];
951                let a = stack[sp - 2];
952                sp -= 1;
953                stack[sp - 1] = match (exact_distance, op) {
954                    (true, _) => FieldInterval::WHOLE,
955                    (false, OP_UNION) => a.min(b),
956                    (false, OP_INTERSECTION) => a.max(b),
957                    (false, OP_SUBTRACTION) => a.max(b.neg()),
958                    (false, _) => {
959                        smooth_min_interval(a, b, FieldInterval::exact(f32::from_bits(arg)))
960                    }
961                };
962            }
963            OP_PUSH_POSE => {
964                let prim = primitives
965                    .get(arg as usize)
966                    .ok_or(FieldError::Primitive(arg))?;
967                if pp == MAX_POSE_DEPTH {
968                    return Err(FieldError::PoseDepth);
969                }
970                points[pp] = p;
971                metrics[pp] = metric;
972                pp += 1;
973                p = local_interval(prim, p);
974                metric = metric.and_then(|metric| metric.through(prim.frame));
975            }
976            OP_POP_POSE => {
977                if pp == 0 {
978                    return Err(FieldError::PoseDepth);
979                }
980                pp -= 1;
981                p = points[pp];
982                metric = metrics[pp];
983            }
984            other => return Err(FieldError::Opcode(other)),
985        }
986    }
987    if sp != 1 {
988        return Err(FieldError::StackUnderflow);
989    }
990    Ok(stack[0])
991}
992
993fn forward_error(
994    program: &[u32],
995    primitives: &[FieldPrimitive],
996    point: [f32; 4],
997    value: f32,
998    kind: FieldKind,
999) -> Result<f32, FieldError> {
1000    let interval = run_interval(program, primitives, point, kind == FieldKind::ExactDistance)?;
1001    if !value.is_finite() || !interval.lo.is_finite() || !interval.hi.is_finite() {
1002        return Ok(f32::INFINITY);
1003    }
1004    let error = FieldInterval::exact(value).sub(interval).abs().hi;
1005    Ok(upper_f32(error).max(FIELD_PROGRAM_ERROR))
1006}
1007
1008fn traverse<const COUNT: bool>(
1009    program: &[u32],
1010    primitives: &[FieldPrimitive],
1011    nodes: &[FieldNode],
1012    point: [f32; 4],
1013    tolerance: f32,
1014    counts: &mut FieldCounts,
1015) -> Result<f32, FieldError> {
1016    if nodes.is_empty() {
1017        return run::<COUNT>(program, primitives, point, counts);
1018    }
1019    let mut best = f32::INFINITY;
1020    let mut index = 0usize;
1021    while index < nodes.len() {
1022        let node = nodes[index];
1023        if COUNT {
1024            counts.node_visits += 1;
1025        }
1026        if node.is_bounded() && node.lower_bound(point) > best + tolerance {
1027            if COUNT {
1028                counts.node_skips += 1;
1029            }
1030            index = node.escape as usize;
1031            continue;
1032        }
1033        if node.is_leaf() {
1034            let range = program
1035                .get(node.start as usize..node.end as usize)
1036                .ok_or(FieldError::Truncated)?;
1037            best = best.min(run::<COUNT>(range, primitives, point, counts)?);
1038        }
1039        index += 1;
1040    }
1041    Ok(best)
1042}
1043
1044pub fn evaluate(
1045    program: &[u32],
1046    primitives: &[FieldPrimitive],
1047    point: [f32; 4],
1048) -> Result<f32, FieldError> {
1049    run::<false>(program, primitives, point, &mut FieldCounts::default())
1050}
1051
1052pub fn evaluate_counted(
1053    program: &[u32],
1054    primitives: &[FieldPrimitive],
1055    point: [f32; 4],
1056    counts: &mut FieldCounts,
1057) -> Result<f32, FieldError> {
1058    run::<true>(program, primitives, point, counts)
1059}
1060
1061/// Skips a subtree whose ball's lower bound exceeds the best value so far by more than `tolerance`; an empty `nodes` slice evaluates the whole program.
1062pub fn evaluate_bounded(
1063    program: &[u32],
1064    primitives: &[FieldPrimitive],
1065    nodes: &[FieldNode],
1066    point: [f32; 4],
1067    tolerance: f32,
1068) -> Result<f32, FieldError> {
1069    traverse::<false>(
1070        program,
1071        primitives,
1072        nodes,
1073        point,
1074        tolerance,
1075        &mut FieldCounts::default(),
1076    )
1077}
1078
1079pub fn evaluate_bounded_counted(
1080    program: &[u32],
1081    primitives: &[FieldPrimitive],
1082    nodes: &[FieldNode],
1083    point: [f32; 4],
1084    tolerance: f32,
1085    counts: &mut FieldCounts,
1086) -> Result<f32, FieldError> {
1087    traverse::<true>(program, primitives, nodes, point, tolerance, counts)
1088}
1089
1090pub(crate) fn evaluate_error(
1091    program: &[u32],
1092    primitives: &[FieldPrimitive],
1093    nodes: &[FieldNode],
1094    point: [f32; 4],
1095    kind: FieldKind,
1096) -> Result<f32, FieldError> {
1097    let value = traverse::<false>(
1098        program,
1099        primitives,
1100        nodes,
1101        point,
1102        0.0,
1103        &mut FieldCounts::default(),
1104    )?;
1105    forward_error(program, primitives, point, value, kind)
1106}
1107
1108const NONE: u32 = u32::MAX;
1109const MAX_OPERANDS: usize = 2;
1110
1111#[derive(Clone, Copy)]
1112struct Node {
1113    entity: Entity,
1114    op: FieldOp,
1115    declared: FieldKind,
1116    first_parent: u32,
1117    edge_start: u32,
1118    edge_len: u32,
1119    record: u32,
1120    marked: u32,
1121    pending: u32,
1122}
1123
1124#[derive(Clone, Copy)]
1125struct Frame {
1126    node: u32,
1127    cursor: u32,
1128    start: u32,
1129    cullable: bool,
1130}
1131
1132#[derive(Clone, Copy)]
1133struct Edge {
1134    operand: u32,
1135    operator: u32,
1136    next: u32,
1137    live: bool,
1138}
1139
1140const EMPTY_EDGE: Edge = Edge {
1141    operand: NONE,
1142    operator: NONE,
1143    next: NONE,
1144    live: false,
1145};
1146
1147pub(crate) struct FieldCompiler {
1148    program: FieldProgram,
1149    staged: FieldProgram,
1150    nodes: Vec<Node>,
1151    edges: Vec<Edge>,
1152    slots: Vec<u32>,
1153    roots: Vec<u32>,
1154    dfs: Vec<Frame>,
1155    cuts: Vec<Cut>,
1156    ordered: Vec<Cut>,
1157    kinds: Vec<FieldKind>,
1158    work: Vec<u32>,
1159    touched: Vec<u32>,
1160    patches: Vec<(usize, FieldPrimitive)>,
1161    fields_cursor: Cursor,
1162    poses_cursor: Cursor,
1163    dirty_stamp: u32,
1164    compiled: bool,
1165}
1166
1167impl Default for FieldCompiler {
1168    fn default() -> Self {
1169        Self::new()
1170    }
1171}
1172
1173impl FieldCompiler {
1174    pub(crate) fn new() -> Self {
1175        Self {
1176            program: FieldProgram::default(),
1177            staged: FieldProgram::default(),
1178            nodes: Vec::new(),
1179            edges: Vec::new(),
1180            slots: Vec::new(),
1181            roots: Vec::new(),
1182            dfs: Vec::new(),
1183            cuts: Vec::new(),
1184            ordered: Vec::new(),
1185            kinds: Vec::new(),
1186            work: Vec::new(),
1187            touched: Vec::new(),
1188            patches: Vec::new(),
1189            fields_cursor: Cursor::default(),
1190            poses_cursor: Cursor::default(),
1191            dirty_stamp: 0,
1192            compiled: false,
1193        }
1194    }
1195
1196    pub(crate) fn program(&self) -> &FieldProgram {
1197        &self.program
1198    }
1199
1200    pub(crate) fn invalidate(&mut self) {
1201        self.compiled = false;
1202        self.fields_cursor = Cursor::default();
1203        self.poses_cursor = Cursor::default();
1204    }
1205
1206    fn node_of(&self, entity: Entity) -> Option<u32> {
1207        let node = *self.slots.get(entity.key().slot() as usize)?;
1208        (node != NONE && self.nodes[node as usize].entity == entity).then_some(node)
1209    }
1210
1211    fn set_slot(&mut self, entity: Entity, node: u32) {
1212        let slot = entity.key().slot() as usize;
1213        if slot >= self.slots.len() {
1214            self.slots.resize(slot + 1, NONE);
1215        }
1216        self.slots[slot] = node;
1217    }
1218
1219    fn has_live_parent(&self, node: u32) -> bool {
1220        self.nodes[node as usize].first_parent != NONE
1221    }
1222
1223    fn link(&mut self, operator: u32, operands: &[Entity]) -> Result<(), DomainError> {
1224        let entity = self.nodes[operator as usize].entity;
1225        if operands.len() != self.nodes[operator as usize].op.operands() {
1226            return Err(DomainError::FieldArity(entity));
1227        }
1228        let edge_start = operator as usize * MAX_OPERANDS;
1229        self.nodes[operator as usize].edge_start = edge_start as u32;
1230        self.nodes[operator as usize].edge_len = operands.len() as u32;
1231        for (offset, &operand) in operands.iter().enumerate() {
1232            let target = self.node_of(operand).ok_or(DomainError::Stale(operand))?;
1233            let edge = (edge_start + offset) as u32;
1234            self.edges[edge as usize] = Edge {
1235                operand: target,
1236                operator,
1237                next: self.nodes[target as usize].first_parent,
1238                live: true,
1239            };
1240            self.nodes[target as usize].first_parent = edge;
1241        }
1242        Ok(())
1243    }
1244
1245    fn detach_parent(&mut self, operand: u32, remove: u32) {
1246        let mut previous = NONE;
1247        let mut edge = self.nodes[operand as usize].first_parent;
1248        while edge != NONE {
1249            let next = self.edges[edge as usize].next;
1250            if edge == remove {
1251                if previous == NONE {
1252                    self.nodes[operand as usize].first_parent = next;
1253                } else {
1254                    self.edges[previous as usize].next = next;
1255                }
1256                return;
1257            }
1258            previous = edge;
1259            edge = next;
1260        }
1261    }
1262
1263    fn unlink(&mut self, operator: u32) {
1264        let node = self.nodes[operator as usize];
1265        for edge in node.edge_start..node.edge_start + node.edge_len {
1266            let operand = self.edges[edge as usize].operand;
1267            self.detach_parent(operand, edge);
1268            self.edges[edge as usize] = EMPTY_EDGE;
1269        }
1270        self.nodes[operator as usize].edge_len = 0;
1271    }
1272
1273    fn operands_match(&self, operator: u32, operands: &[Entity]) -> bool {
1274        let node = self.nodes[operator as usize];
1275        if node.edge_len as usize != operands.len() {
1276            return false;
1277        }
1278        (0..node.edge_len).all(|i| {
1279            let edge = self.edges[(node.edge_start + i) as usize];
1280            edge.live && self.nodes[edge.operand as usize].entity == operands[i as usize]
1281        })
1282    }
1283
1284    fn rebuild_index(&mut self, fields: &Store<Field>) -> Result<u32, DomainError> {
1285        self.nodes.clear();
1286        self.edges.clear();
1287        self.slots.clear();
1288        for (entity, field) in fields.iter() {
1289            validate_op(field.op)?;
1290            let node = self.nodes.len() as u32;
1291            self.nodes.push(Node {
1292                entity,
1293                op: field.op,
1294                declared: field.kind,
1295                first_parent: NONE,
1296                edge_start: NONE,
1297                edge_len: 0,
1298                record: NONE,
1299                marked: 0,
1300                pending: 0,
1301            });
1302            self.set_slot(entity, node);
1303        }
1304        self.edges
1305            .resize(self.nodes.len() * MAX_OPERANDS, EMPTY_EDGE);
1306        let mut work = 0;
1307        for index in 0..self.nodes.len() as u32 {
1308            let entity = self.nodes[index as usize].entity;
1309            let field = fields.get(entity).ok_or(DomainError::Stale(entity))?;
1310            work += field.operands.len() as u32;
1311            self.link(index, &field.operands)?;
1312        }
1313        Ok(work)
1314    }
1315
1316    fn mark_ancestors(&mut self) -> u32 {
1317        self.dirty_stamp = match self.dirty_stamp.checked_add(1) {
1318            Some(stamp) => stamp,
1319            None => {
1320                for node in &mut self.nodes {
1321                    node.marked = 0;
1322                }
1323                1
1324            }
1325        };
1326        let stamp = self.dirty_stamp;
1327        self.work.clear();
1328        for i in 0..self.touched.len() {
1329            let node = self.touched[i];
1330            if self.nodes[node as usize].marked != stamp {
1331                self.nodes[node as usize].marked = stamp;
1332                self.work.push(node);
1333            }
1334        }
1335        let mut affected = 0;
1336        let mut read = 0;
1337        while read < self.work.len() {
1338            let node = self.work[read];
1339            read += 1;
1340            let mut edge = self.nodes[node as usize].first_parent;
1341            while edge != NONE {
1342                let e = self.edges[edge as usize];
1343                if e.live && self.nodes[e.operator as usize].marked != stamp {
1344                    self.nodes[e.operator as usize].marked = stamp;
1345                    affected += 1;
1346                    self.work.push(e.operator);
1347                }
1348                edge = e.next;
1349            }
1350        }
1351        affected
1352    }
1353
1354    fn record_for<S: DomainSpace>(
1355        &mut self,
1356        node: u32,
1357        space: &S,
1358        poses: &Store<Pose<S>>,
1359    ) -> Result<u32, DomainError> {
1360        if self.nodes[node as usize].record != NONE {
1361            return Ok(self.nodes[node as usize].record);
1362        }
1363        let (entity, op) = {
1364            let node = self.nodes[node as usize];
1365            (node.entity, node.op)
1366        };
1367        let record = self.program.primitives.len() as u32;
1368        self.program
1369            .primitives
1370            .push(primitive_record(space, poses, entity, op)?);
1371        self.nodes[node as usize].record = record;
1372        Ok(record)
1373    }
1374
1375    fn patch<S: DomainSpace>(
1376        &mut self,
1377        space: &S,
1378        poses: &Store<Pose<S>>,
1379    ) -> Result<u32, DomainError> {
1380        self.patches.clear();
1381        for i in 0..self.touched.len() {
1382            let node = self.nodes[self.touched[i] as usize];
1383            if node.record == NONE {
1384                continue;
1385            }
1386            self.patches.push((
1387                node.record as usize,
1388                primitive_record(space, poses, node.entity, node.op)?,
1389            ));
1390        }
1391        for &(record, primitive) in &self.patches {
1392            self.program.primitives[record] = primitive;
1393        }
1394        Ok(self.patches.len() as u32)
1395    }
1396
1397    fn emit<S: DomainSpace>(
1398        &mut self,
1399        space: &S,
1400        poses: &Store<Pose<S>>,
1401    ) -> Result<u32, DomainError> {
1402        std::mem::swap(&mut self.program, &mut self.staged);
1403        let result = self.emit_staged(space, poses);
1404        if result.is_err() {
1405            std::mem::swap(&mut self.program, &mut self.staged);
1406        }
1407        result
1408    }
1409
1410    fn emit_staged<S: DomainSpace>(
1411        &mut self,
1412        space: &S,
1413        poses: &Store<Pose<S>>,
1414    ) -> Result<u32, DomainError> {
1415        self.program.program.clear();
1416        self.program.primitives.clear();
1417        for node in &mut self.nodes {
1418            node.record = NONE;
1419            node.pending = node.edge_len;
1420        }
1421        self.check_acyclic()?;
1422        let mut roots = std::mem::take(&mut self.roots);
1423        roots.clear();
1424        roots.extend((0..self.nodes.len() as u32).filter(|&node| !self.has_live_parent(node)));
1425        roots.sort_unstable_by_key(|&node| self.nodes[node as usize].entity);
1426        self.roots = roots;
1427
1428        self.kinds.clear();
1429        self.dfs.clear();
1430        self.cuts.clear();
1431        let mut height = 0u32;
1432        let mut peak = 0u32;
1433        let mut pose_depth = 0usize;
1434        for index in 0..self.roots.len() {
1435            self.dfs.push(Frame {
1436                node: self.roots[index],
1437                cursor: 0,
1438                start: self.program.program.len() as u32,
1439                cullable: true,
1440            });
1441            while !self.dfs.is_empty() {
1442                let top = self.dfs.len() - 1;
1443                let frame = self.dfs[top];
1444                let (node, cursor) = (frame.node, frame.cursor);
1445                let op = self.nodes[node as usize].op;
1446                if cursor == 0 && matches!(op, FieldOp::Transform) {
1447                    let record = self.record_for(node, space, poses)?;
1448                    self.push_instruction(OP_PUSH_POSE, record)?;
1449                    pose_depth += 1;
1450                    if pose_depth > MAX_POSE_DEPTH {
1451                        return Err(DomainError::Unsupported("field pose depth"));
1452                    }
1453                }
1454                if (cursor as usize) < op.operands() {
1455                    self.dfs[top].cursor = cursor + 1;
1456                    let start = self.nodes[node as usize].edge_start;
1457                    let child = self.edges[(start + cursor) as usize].operand;
1458                    self.dfs.push(Frame {
1459                        node: child,
1460                        cursor: 0,
1461                        start: self.program.program.len() as u32,
1462                        cullable: frame.cullable && matches!(op, FieldOp::Union),
1463                    });
1464                    continue;
1465                }
1466                match op {
1467                    FieldOp::Transform => {
1468                        self.push_instruction(OP_POP_POSE, 0)?;
1469                        pose_depth -= 1;
1470                        let operand = self.kinds.pop().unwrap_or(FieldKind::ExactDistance);
1471                        self.kinds.push(op.result_kind(&[operand]));
1472                    }
1473                    FieldOp::Union
1474                    | FieldOp::Intersection
1475                    | FieldOp::Subtraction
1476                    | FieldOp::SmoothUnion { .. } => {
1477                        let argument = match op {
1478                            FieldOp::SmoothUnion { radius } => radius.to_bits(),
1479                            _ => 0,
1480                        };
1481                        self.push_instruction(op.opcode(), argument)?;
1482                        let right = self.kinds.pop().unwrap_or(FieldKind::ExactDistance);
1483                        let left = self.kinds.pop().unwrap_or(FieldKind::ExactDistance);
1484                        self.kinds.push(op.result_kind(&[left, right]));
1485                        height -= 1;
1486                    }
1487                    _ => {
1488                        let record = self.record_for(node, space, poses)?;
1489                        self.push_instruction(op.opcode(), record)?;
1490                        self.kinds.push(self.nodes[node as usize].declared);
1491                        height += 1;
1492                        peak = peak.max(height);
1493                    }
1494                }
1495                if frame.cullable && !matches!(op, FieldOp::Union) {
1496                    self.cuts.push(Cut {
1497                        node,
1498                        start: frame.start,
1499                        end: self.program.program.len() as u32,
1500                        implicit: self.kinds.last().copied() == Some(FieldKind::Implicit),
1501                        ball: UNBOUNDED,
1502                    });
1503                }
1504                self.dfs.pop();
1505            }
1506            if index > 0 {
1507                self.push_instruction(OP_UNION, 0)?;
1508                let right = self.kinds.pop().unwrap_or(FieldKind::ExactDistance);
1509                let left = self.kinds.pop().unwrap_or(FieldKind::ExactDistance);
1510                self.kinds.push(FieldOp::Union.result_kind(&[left, right]));
1511                height -= 1;
1512            }
1513        }
1514        if peak as usize > MAX_STACK {
1515            return Err(DomainError::Unsupported("field stack depth"));
1516        }
1517        self.program.stack = peak;
1518        self.program.kind = self.kinds.pop().unwrap_or(FieldKind::ConservativeBound);
1519        Ok((self.program.program.len() / 2) as u32 + self.program.primitives.len() as u32)
1520    }
1521
1522    fn rebuild_bounds(&mut self, extruded_w: bool, changed_only: bool) -> u32 {
1523        for cut in &mut self.cuts {
1524            if changed_only && self.nodes[cut.node as usize].marked != self.dirty_stamp {
1525                continue;
1526            }
1527            cut.ball = if cut.implicit {
1528                UNBOUNDED
1529            } else {
1530                match self
1531                    .program
1532                    .program
1533                    .get(cut.start as usize..cut.end as usize)
1534                {
1535                    Some(range) => subtree_ball(&self.program.primitives, range, extruded_w),
1536                    None => UNBOUNDED,
1537                }
1538            };
1539        }
1540        let mut ordered = std::mem::take(&mut self.ordered);
1541        ordered.clear();
1542        ordered.extend(self.cuts.iter().copied().filter(|cut| cut.ball.bounded()));
1543        let bounded = ordered.len();
1544        ordered.extend(self.cuts.iter().copied().filter(|cut| !cut.ball.bounded()));
1545        self.program.nodes.clear();
1546        if bounded > 0 {
1547            build_tree(&mut ordered[..bounded], &mut self.program.nodes);
1548            for cut in &ordered[bounded..] {
1549                let escape = self.program.nodes.len() as u32 + 1;
1550                self.program.nodes.push(FieldNode {
1551                    center: [0.0; 4],
1552                    radius: UNBOUNDED.radius,
1553                    scale: UNBOUNDED.scale,
1554                    start: cut.start,
1555                    end: cut.end,
1556                    escape,
1557                });
1558            }
1559        }
1560        self.ordered = ordered;
1561        self.program.nodes.len() as u32
1562    }
1563
1564    // Kahn 1962, "Topological sorting of large networks", CACM 5(11).
1565    fn check_acyclic(&mut self) -> Result<(), DomainError> {
1566        self.work.clear();
1567        for index in 0..self.nodes.len() as u32 {
1568            if self.nodes[index as usize].pending == 0 {
1569                self.work.push(index);
1570            }
1571        }
1572        let mut read = 0;
1573        while read < self.work.len() {
1574            let node = self.work[read];
1575            read += 1;
1576            let mut edge = self.nodes[node as usize].first_parent;
1577            while edge != NONE {
1578                let e = self.edges[edge as usize];
1579                if e.live {
1580                    self.nodes[e.operator as usize].pending -= 1;
1581                    if self.nodes[e.operator as usize].pending == 0 {
1582                        self.work.push(e.operator);
1583                    }
1584                }
1585                edge = e.next;
1586            }
1587        }
1588        match self.nodes.iter().find(|node| node.pending != 0) {
1589            Some(node) => Err(DomainError::FieldCycle(node.entity)),
1590            None => Ok(()),
1591        }
1592    }
1593
1594    fn push_instruction(&mut self, op: u32, argument: u32) -> Result<(), DomainError> {
1595        if self.program.program.len() + 2 > MAX_PROGRAM_WORDS {
1596            return Err(DomainError::Unsupported("field program size"));
1597        }
1598        self.program.program.push(op);
1599        self.program.program.push(argument);
1600        Ok(())
1601    }
1602
1603    pub(crate) fn compile<S: DomainSpace>(
1604        &mut self,
1605        space: &S,
1606        fields: &Store<Field>,
1607        poses: &Store<Pose<S>>,
1608    ) -> Result<FieldCost, DomainError> {
1609        if !space.is_chart_flat() {
1610            self.compiled = false;
1611            return Err(DomainError::Unsupported("curved field chart"));
1612        }
1613        let dimension = space.chart_dimension();
1614        let structural = fields.changed_since(&mut self.fields_cursor);
1615        let placed = poses.changed_since(&mut self.poses_cursor);
1616        if self.compiled && !structural && !placed {
1617            return Ok(FieldCost::default());
1618        }
1619        if dimension < 4
1620            && (!self.compiled || structural)
1621            && fields.iter().any(|(_, field)| {
1622                matches!(
1623                    field.op,
1624                    FieldOp::HyperSphere { .. } | FieldOp::HalfSpace4 { .. }
1625                )
1626            })
1627        {
1628            return Err(DomainError::Unsupported("field primitive dimension"));
1629        }
1630
1631        let mut cost = FieldCost::default();
1632        let mut rebuild = !self.compiled;
1633        self.compiled = false;
1634        self.touched.clear();
1635        if structural {
1636            let changes = fields.changes(&mut self.fields_cursor);
1637            rebuild |= changes.is_resync();
1638            for change in changes {
1639                match change {
1640                    Change::Row(entity, _) => match self.node_of(entity) {
1641                        Some(node) => {
1642                            self.touched.push(node);
1643                            cost.changed_inputs += 1;
1644                        }
1645                        None => {
1646                            cost.changed_inputs += 1;
1647                            rebuild = true;
1648                        }
1649                    },
1650                    Change::Removed(_) => {
1651                        cost.changed_inputs += 1;
1652                        rebuild = true;
1653                    }
1654                }
1655            }
1656        }
1657        let edited = self.touched.len();
1658        if placed {
1659            let changes = poses.changes(&mut self.poses_cursor);
1660            for change in changes {
1661                match change {
1662                    Change::Row(entity, _) => {
1663                        if let Some(node) = self.node_of(entity) {
1664                            self.touched.push(node);
1665                            cost.changed_inputs += 1;
1666                        }
1667                    }
1668                    Change::Removed(removal) => {
1669                        if let Some(node) = self.node_of(removal.entity) {
1670                            self.touched.push(node);
1671                            cost.changed_inputs += 1;
1672                        }
1673                    }
1674                }
1675            }
1676        }
1677
1678        if rebuild {
1679            cost.index_maintenance = self.rebuild_index(fields)?;
1680            cost.full_rebuild = true;
1681            self.touched.clear();
1682        } else {
1683            for i in 0..edited {
1684                let node = self.touched[i];
1685                let entity = self.nodes[node as usize].entity;
1686                let field = fields.get(entity).ok_or(DomainError::Stale(entity))?;
1687                validate_op(field.op)?;
1688                self.nodes[node as usize].op = field.op;
1689                self.nodes[node as usize].declared = field.kind;
1690                if !self.operands_match(node, &field.operands) {
1691                    cost.index_maintenance += self.nodes[node as usize].edge_len;
1692                    cost.index_maintenance += field.operands.len() as u32;
1693                    self.unlink(node);
1694                    self.link(node, &field.operands)?;
1695                }
1696            }
1697        }
1698
1699        cost.affected_dependencies = self.mark_ancestors();
1700        cost.program_layout = if rebuild || edited > 0 {
1701            self.emit(space, poses)?
1702        } else {
1703            self.patch(space, poses)?
1704        };
1705        cost.index_maintenance += self.rebuild_bounds(dimension > 3, !rebuild && edited == 0);
1706        self.program.dimension = dimension;
1707        self.compiled = true;
1708        Ok(cost)
1709    }
1710}
1711
1712fn primitive_record<S: DomainSpace>(
1713    space: &S,
1714    poses: &Store<Pose<S>>,
1715    entity: Entity,
1716    op: FieldOp,
1717) -> Result<FieldPrimitive, DomainError> {
1718    validate_op(op)?;
1719    let pose = poses.get(entity).ok_or(DomainError::Stale(entity))?;
1720    let chart = space.chart_pose(pose);
1721    let mut record = FieldPrimitive {
1722        frame: chart.frame,
1723        translation: chart.coordinates,
1724        params: [0.0; 4],
1725    };
1726    match op {
1727        FieldOp::Sphere { radius } | FieldOp::HyperSphere { radius } => record.params[0] = radius,
1728        FieldOp::Box { half_extents } => {
1729            record.params[..3].copy_from_slice(&half_extents);
1730        }
1731        FieldOp::HalfSpace { normal, offset } => {
1732            let plane = unit_plane([normal[0], normal[1], normal[2], 0.0], offset)?;
1733            record.params = plane.0;
1734            shift_along(&mut record, plane.0, plane.1);
1735        }
1736        FieldOp::HalfSpace4 { normal, offset } => {
1737            let plane = unit_plane(normal, offset)?;
1738            record.params = plane.0;
1739            shift_along(&mut record, plane.0, plane.1);
1740        }
1741        FieldOp::Transform => {}
1742        _ => return Err(DomainError::FieldArity(entity)),
1743    }
1744    if record
1745        .frame
1746        .iter()
1747        .flatten()
1748        .chain(&record.translation)
1749        .chain(&record.params)
1750        .any(|component| !component.is_finite())
1751    {
1752        return Err(DomainError::InvalidCoordinate("field primitive"));
1753    }
1754    Ok(record)
1755}
1756
1757fn validate_op(op: FieldOp) -> Result<(), DomainError> {
1758    match op {
1759        FieldOp::Sphere { radius } | FieldOp::HyperSphere { radius }
1760            if !radius.is_finite() || radius <= 0.0 =>
1761        {
1762            Err(DomainError::InvalidCoordinate("field radius"))
1763        }
1764        FieldOp::Box { half_extents }
1765            if half_extents
1766                .into_iter()
1767                .any(|extent| !extent.is_finite() || extent <= 0.0) =>
1768        {
1769            Err(DomainError::InvalidCoordinate("field half extent"))
1770        }
1771        FieldOp::HalfSpace { offset, .. } | FieldOp::HalfSpace4 { offset, .. }
1772            if !offset.is_finite() =>
1773        {
1774            Err(DomainError::InvalidCoordinate("field offset"))
1775        }
1776        FieldOp::SmoothUnion { radius } if !radius.is_finite() || radius <= 0.0 => {
1777            Err(DomainError::InvalidCoordinate("field smoothing radius"))
1778        }
1779        _ => Ok(()),
1780    }
1781}
1782
1783fn unit_plane(normal: [f32; 4], offset: f32) -> Result<([f32; 4], f32), DomainError> {
1784    let length = normal
1785        .iter()
1786        .map(|component| component * component)
1787        .sum::<f32>()
1788        .sqrt();
1789    if !length.is_finite() || length < 1e-6 || !offset.is_finite() {
1790        return Err(DomainError::InvalidCoordinate("field normal"));
1791    }
1792    Ok((normal.map(|component| component / length), offset / length))
1793}
1794
1795fn shift_along(record: &mut FieldPrimitive, normal: [f32; 4], offset: f32) {
1796    for axis in 0..4 {
1797        let mut sum = 0.0;
1798        for (component, column) in normal.iter().zip(record.frame) {
1799            sum += column[axis] * component;
1800        }
1801        record.translation[axis] += sum * offset;
1802    }
1803}
1804
1805#[cfg(test)]
1806mod tests {
1807    use loam_math::EuclideanR3;
1808    use loam_time::alloc::bytes_allocated_by;
1809
1810    use super::*;
1811    use crate::domain::{Domain, DomainBuilder, DomainId, TypedDomain};
1812    use crate::entity::{Entities, Entity, Epoch, RuntimeId, SceneId};
1813    use crate::store::DEFAULT_LOG_CAPACITY;
1814    use crate::view::Vec3;
1815
1816    fn test_entities() -> Entities {
1817        Entities::new(SceneId {
1818            runtime: RuntimeId::allocate(),
1819            epoch: Epoch::default(),
1820        })
1821    }
1822
1823    struct Fixture {
1824        entities: Entities,
1825        domain: TypedDomain<EuclideanR3>,
1826    }
1827
1828    impl Fixture {
1829        fn new() -> Self {
1830            let entities = test_entities();
1831            let domain = DomainBuilder::new("fields", EuclideanR3)
1832                .tracked(DEFAULT_LOG_CAPACITY)
1833                .fields()
1834                .build(DomainId::new(0), entities.scene());
1835            Self { entities, domain }
1836        }
1837
1838        fn spawn(&mut self, at: Vec3, kind: FieldKind, op: FieldOp, operands: &[Entity]) -> Entity {
1839            let entity = self.entities.spawn();
1840            self.domain
1841                .attach_pose(entity, Pose::at(at))
1842                .expect("pose row");
1843            self.domain
1844                .attach_field(
1845                    entity,
1846                    Field {
1847                        kind,
1848                        op,
1849                        operands: operands.to_vec(),
1850                    },
1851                )
1852                .expect("field row");
1853            entity
1854        }
1855
1856        fn compile(&mut self) -> Result<FieldCost, DomainError> {
1857            self.domain.compile_fields()
1858        }
1859
1860        fn compile_with(&self, compiler: &mut FieldCompiler) -> Result<FieldCost, DomainError> {
1861            compiler.compile(
1862                &EuclideanR3,
1863                self.domain.fields().expect("field store"),
1864                self.domain.poses(),
1865            )
1866        }
1867
1868        fn move_to(&mut self, entity: Entity, to: Vec3) {
1869            self.domain.set_point(entity, to).expect("pose row");
1870        }
1871    }
1872
1873    fn sphere(radius: f32) -> FieldOp {
1874        FieldOp::Sphere { radius }
1875    }
1876
1877    #[test]
1878    fn a_missing_operand_is_refused_and_names_the_operand() {
1879        let mut fixture = Fixture::new();
1880        let ghost = fixture.entities.spawn();
1881        fixture.spawn(Vec3::ZERO, FieldKind::ExactDistance, sphere(1.0), &[]);
1882        let root = fixture.spawn(
1883            Vec3::ZERO,
1884            FieldKind::ExactDistance,
1885            FieldOp::Transform,
1886            &[ghost],
1887        );
1888        assert_eq!(fixture.compile(), Err(DomainError::Stale(ghost)));
1889        assert_ne!(root, ghost);
1890    }
1891
1892    #[test]
1893    fn a_failed_compile_repeats_then_recovers_without_replacing_the_valid_program() {
1894        let mut fixture = Fixture::new();
1895        let leaf = fixture.spawn(Vec3::ZERO, FieldKind::ExactDistance, sphere(1.0), &[]);
1896        let up = fixture.spawn(
1897            Vec3::ZERO,
1898            FieldKind::ExactDistance,
1899            FieldOp::Union,
1900            &[leaf, leaf],
1901        );
1902        let down = fixture.spawn(
1903            Vec3::ZERO,
1904            FieldKind::ExactDistance,
1905            FieldOp::Union,
1906            &[up, leaf],
1907        );
1908        fixture.compile().expect("valid compile");
1909        let point = [3.0, 0.0, 0.0, 0.0];
1910        let valid = fixture.domain.field_program().evaluate(point).unwrap();
1911
1912        fixture.domain.field_mut(up).unwrap().operands = vec![down, leaf];
1913        assert_eq!(fixture.compile(), Err(DomainError::FieldCycle(up)));
1914        assert_eq!(fixture.compile(), Err(DomainError::FieldCycle(up)));
1915        assert_eq!(
1916            fixture.domain.field_program().evaluate(point).unwrap(),
1917            valid
1918        );
1919
1920        fixture.domain.field_mut(up).unwrap().operands = vec![leaf, leaf];
1921        assert!(fixture.compile().expect("repaired compile").full_rebuild);
1922        assert_eq!(
1923            fixture.domain.field_program().evaluate(point).unwrap(),
1924            valid
1925        );
1926
1927        let mut fields = Store::tracked(DEFAULT_LOG_CAPACITY);
1928        let mut poses = Store::tracked(DEFAULT_LOG_CAPACITY);
1929        fields.bind(fixture.entities.scene());
1930        poses.bind(fixture.entities.scene());
1931        fields
1932            .insert(
1933                &fixture.entities,
1934                leaf,
1935                Field {
1936                    kind: FieldKind::ExactDistance,
1937                    op: sphere(1.0),
1938                    operands: Vec::new(),
1939                },
1940            )
1941            .unwrap();
1942        poses
1943            .insert(&fixture.entities, leaf, Pose::at(Vec3::ZERO))
1944            .unwrap();
1945        let mut compiler = FieldCompiler::new();
1946        compiler.compile(&EuclideanR3, &fields, &poses).unwrap();
1947        let pose_valid = compiler.program().evaluate(point).unwrap();
1948        poses.remove(leaf).unwrap();
1949        assert_eq!(
1950            compiler.compile(&EuclideanR3, &fields, &poses),
1951            Err(DomainError::Stale(leaf))
1952        );
1953        assert_eq!(
1954            compiler.compile(&EuclideanR3, &fields, &poses),
1955            Err(DomainError::Stale(leaf))
1956        );
1957        assert_eq!(compiler.program().evaluate(point).unwrap(), pose_valid);
1958    }
1959
1960    #[test]
1961    fn moving_one_primitive_marks_only_the_operators_above_it() {
1962        let mut fixture = Fixture::new();
1963        let left = fixture.spawn(-Vec3::X, FieldKind::ExactDistance, sphere(0.5), &[]);
1964        let right = fixture.spawn(Vec3::X, FieldKind::ExactDistance, sphere(0.5), &[]);
1965        let far = fixture.spawn(Vec3::Y * 4.0, FieldKind::ExactDistance, sphere(0.5), &[]);
1966        let inner = fixture.spawn(
1967            Vec3::ZERO,
1968            FieldKind::ExactDistance,
1969            FieldOp::Union,
1970            &[left, right],
1971        );
1972        fixture.spawn(
1973            Vec3::ZERO,
1974            FieldKind::ExactDistance,
1975            FieldOp::Union,
1976            &[inner, far],
1977        );
1978        assert!(fixture.compile().expect("first compile").full_rebuild);
1979        assert!(fixture.compile().expect("settle").is_idle());
1980
1981        fixture.move_to(left, Vec3::new(-2.0, 0.0, 0.0));
1982        let cost = fixture.compile().expect("incremental compile");
1983        assert_eq!(
1984            cost,
1985            FieldCost {
1986                changed_inputs: 1,
1987                affected_dependencies: 2,
1988                program_layout: 1,
1989                index_maintenance: 5,
1990                full_rebuild: false,
1991            }
1992        );
1993
1994        fixture.move_to(far, Vec3::new(0.0, 5.0, 0.0));
1995        let cost = fixture.compile().expect("incremental compile");
1996        assert_eq!(cost.affected_dependencies, 1);
1997    }
1998
1999    #[test]
2000    fn an_unchanged_store_compiles_with_no_work_and_no_rebuild() {
2001        let mut fixture = Fixture::new();
2002        let leaf = fixture.spawn(Vec3::ZERO, FieldKind::ExactDistance, sphere(1.0), &[]);
2003        fixture.spawn(
2004            Vec3::ZERO,
2005            FieldKind::ExactDistance,
2006            FieldOp::Transform,
2007            &[leaf],
2008        );
2009        assert!(fixture.compile().expect("first compile").full_rebuild);
2010        let words = fixture.domain.field_program().program.len();
2011        for _ in 0..4 {
2012            assert!(fixture.compile().expect("idle compile").is_idle());
2013        }
2014        assert_eq!(fixture.domain.field_program().program.len(), words);
2015    }
2016
2017    #[test]
2018    fn repeated_operand_edits_keep_index_storage_and_work_bounded() {
2019        let mut fixture = Fixture::new();
2020        let mut compiler = FieldCompiler::new();
2021        let left = fixture.spawn(-Vec3::X, FieldKind::ExactDistance, sphere(0.5), &[]);
2022        let right = fixture.spawn(Vec3::X, FieldKind::ExactDistance, sphere(0.5), &[]);
2023        let root = fixture.spawn(
2024            Vec3::ZERO,
2025            FieldKind::ExactDistance,
2026            FieldOp::Union,
2027            &[left, right],
2028        );
2029        fixture.compile_with(&mut compiler).expect("first compile");
2030        fixture.compile_with(&mut compiler).expect("settle");
2031
2032        let edge_slots = compiler.edges.len();
2033        for step in 0..128 {
2034            fixture.domain.field_mut(root).expect("root row").operands = if step % 2 == 0 {
2035                vec![right, left]
2036            } else {
2037                vec![left, right]
2038            };
2039            let cost = fixture.compile_with(&mut compiler).expect("operand edit");
2040            assert!(!cost.full_rebuild);
2041            assert_eq!(cost.index_maintenance, 7);
2042            assert!(cost.program_layout > 0);
2043            assert_eq!(compiler.edges.len(), edge_slots);
2044        }
2045
2046        fixture.move_to(left, -Vec3::Y);
2047        assert_eq!(
2048            fixture
2049                .compile_with(&mut compiler)
2050                .unwrap()
2051                .affected_dependencies,
2052            1
2053        );
2054    }
2055
2056    #[test]
2057    fn a_smooth_union_of_exact_leaves_reports_a_conservative_bound() {
2058        let mut fixture = Fixture::new();
2059        let left = fixture.spawn(-Vec3::X, FieldKind::ExactDistance, sphere(0.5), &[]);
2060        let right = fixture.spawn(Vec3::X, FieldKind::ExactDistance, sphere(0.5), &[]);
2061        fixture.spawn(
2062            Vec3::ZERO,
2063            FieldKind::ExactDistance,
2064            FieldOp::SmoothUnion { radius: 0.4 },
2065            &[left, right],
2066        );
2067        fixture.compile().expect("compile");
2068        let program = fixture.domain.field_program();
2069        assert_eq!(program.kind, FieldKind::ConservativeBound);
2070        assert_eq!(program.stack, 2);
2071
2072        let closed_form = |p: Vec3| {
2073            let a = (p - Vec3::new(-1.0, 0.0, 0.0)).length() - 0.5;
2074            let b = (p - Vec3::new(1.0, 0.0, 0.0)).length() - 0.5;
2075            smooth_min(a, b, 0.4)
2076        };
2077        for probe in [
2078            Vec3::ZERO,
2079            Vec3::new(0.3, 0.2, 0.0),
2080            Vec3::new(-1.4, 0.0, 0.1),
2081        ] {
2082            let (value, kind) = program
2083                .evaluate([probe.x, probe.y, probe.z, 0.0])
2084                .expect("well-formed program");
2085            assert_eq!(kind, FieldKind::ConservativeBound);
2086            assert!(
2087                (value - closed_form(probe)).abs() < 1e-6,
2088                "{probe:?}: interpreter {value} against closed form {}",
2089                closed_form(probe)
2090            );
2091        }
2092    }
2093
2094    #[test]
2095    fn invalid_field_dimensions_and_zero_smoothing_are_refused() {
2096        for op in [
2097            sphere(0.0),
2098            sphere(f32::INFINITY),
2099            FieldOp::Box {
2100                half_extents: [1.0, -1.0, 1.0],
2101            },
2102            FieldOp::HalfSpace {
2103                normal: Vec3::Y.to_array(),
2104                offset: f32::NAN,
2105            },
2106        ] {
2107            let mut fixture = Fixture::new();
2108            fixture.spawn(Vec3::ZERO, FieldKind::ExactDistance, op, &[]);
2109            assert!(matches!(
2110                fixture.compile(),
2111                Err(DomainError::InvalidCoordinate(_))
2112            ));
2113        }
2114
2115        let mut fixture = Fixture::new();
2116        let left = fixture.spawn(-Vec3::X, FieldKind::ExactDistance, sphere(1.0), &[]);
2117        let right = fixture.spawn(Vec3::X, FieldKind::ExactDistance, sphere(1.0), &[]);
2118        fixture.spawn(
2119            Vec3::ZERO,
2120            FieldKind::ExactDistance,
2121            FieldOp::SmoothUnion { radius: 0.0 },
2122            &[left, right],
2123        );
2124        assert_eq!(
2125            fixture.compile(),
2126            Err(DomainError::InvalidCoordinate("field smoothing radius"))
2127        );
2128    }
2129
2130    #[test]
2131    fn a_pose_transform_places_its_whole_subtree() {
2132        let mut fixture = Fixture::new();
2133        let leaf = fixture.spawn(Vec3::ZERO, FieldKind::ExactDistance, sphere(0.5), &[]);
2134        fixture.spawn(
2135            Vec3::new(3.0, 0.0, 0.0),
2136            FieldKind::ExactDistance,
2137            FieldOp::Transform,
2138            &[leaf],
2139        );
2140        fixture.compile().expect("compile");
2141        let program = fixture.domain.field_program();
2142        assert!((program.evaluate([3.0, 0.0, 0.0, 0.0]).expect("eval").0 + 0.5).abs() < 1e-6);
2143        assert!((program.evaluate([0.0, 0.0, 0.0, 0.0]).expect("eval").0 - 2.5).abs() < 1e-6);
2144    }
2145
2146    #[test]
2147    fn a_posed_half_space_matches_the_domain_isometry() {
2148        use loam_math::{EuclideanR4, Iso4Flat, IsometryGroup, Rotor4};
2149
2150        let half = std::f32::consts::FRAC_1_SQRT_2;
2151        let pose = Iso4Flat {
2152            rotation: Rotor4 {
2153                s: half,
2154                xy: half,
2155                ..Rotor4::IDENTITY
2156            },
2157            translation: crate::view::Vec4::new(0.3, -0.2, 1.5, 0.4),
2158        };
2159        let normal = [0.0, 2.0, 0.0, 0.0];
2160        let offset = 0.5;
2161
2162        let mut entities = test_entities();
2163        let mut domain = DomainBuilder::new("r4", EuclideanR4)
2164            .tracked(DEFAULT_LOG_CAPACITY)
2165            .fields()
2166            .build(DomainId::new(0), entities.scene());
2167        let entity = entities.spawn();
2168        domain
2169            .attach_pose(entity, Pose::from(pose))
2170            .expect("pose row");
2171        domain
2172            .attach_field(
2173                entity,
2174                Field {
2175                    kind: FieldKind::ExactDistance,
2176                    op: FieldOp::HalfSpace4 { normal, offset },
2177                    operands: Vec::new(),
2178                },
2179            )
2180            .expect("field row");
2181        domain.compile_fields().expect("compile");
2182
2183        let space = EuclideanR4;
2184        let inverse = space.iso_inverse(pose);
2185        let unit = crate::view::Vec4::from_array(normal).normalize();
2186        for point in [
2187            crate::view::Vec4::ZERO,
2188            crate::view::Vec4::new(1.0, -0.5, 0.25, 0.75),
2189            crate::view::Vec4::new(-2.0, 3.0, -1.0, 0.0),
2190        ] {
2191            let expected = space.iso_apply(inverse, point).dot(unit) - offset / 2.0;
2192            let value = domain
2193                .field_program()
2194                .evaluate(point.to_array())
2195                .expect("eval")
2196                .0;
2197            assert!(
2198                (value - expected).abs() < 1e-5,
2199                "{point:?}: record transform gave {value}, the isometry gives {expected}"
2200            );
2201        }
2202    }
2203
2204    #[test]
2205    fn a_warmed_incremental_compile_allocates_nothing() {
2206        let mut fixture = Fixture::new();
2207        let leaves: Vec<Entity> = (0..32)
2208            .map(|i| {
2209                fixture.spawn(
2210                    Vec3::new(i as f32, 0.0, 0.0),
2211                    FieldKind::ExactDistance,
2212                    sphere(0.4),
2213                    &[],
2214                )
2215            })
2216            .collect();
2217        let mut level = leaves.clone();
2218        while level.len() > 1 {
2219            level = level
2220                .chunks(2)
2221                .map(|pair| {
2222                    fixture.spawn(Vec3::ZERO, FieldKind::ExactDistance, FieldOp::Union, pair)
2223                })
2224                .collect();
2225        }
2226        for _ in 0..4 {
2227            fixture.move_to(leaves[0], Vec3::new(0.5, 0.0, 0.0));
2228            fixture.compile().expect("warm up");
2229        }
2230        let bytes = bytes_allocated_by(|| {
2231            for step in 0..32 {
2232                fixture.move_to(
2233                    leaves[step % leaves.len()],
2234                    Vec3::new(step as f32, 1.0, 0.0),
2235                );
2236                let cost = fixture.compile().expect("incremental compile");
2237                assert!(!cost.full_rebuild);
2238                assert_eq!(cost.program_layout, 1);
2239            }
2240        })
2241        .expect("the counting allocator is installed");
2242        assert_eq!(
2243            bytes, 0,
2244            "32 warmed incremental compiles allocated {bytes} bytes"
2245        );
2246    }
2247
2248    #[test]
2249    fn a_smooth_union_bound_that_drops_its_radius_culls_the_blend_surface() {
2250        let mut fixture = Fixture::new();
2251        let left = fixture.spawn(-Vec3::X, FieldKind::ExactDistance, sphere(0.5), &[]);
2252        let right = fixture.spawn(Vec3::X, FieldKind::ExactDistance, sphere(0.5), &[]);
2253        let blend = fixture.spawn(
2254            Vec3::ZERO,
2255            FieldKind::ExactDistance,
2256            FieldOp::SmoothUnion { radius: 5.0 },
2257            &[left, right],
2258        );
2259        let decoy = fixture.spawn(
2260            Vec3::new(-9.0, 4.0, 0.0),
2261            FieldKind::ExactDistance,
2262            sphere(6.55),
2263            &[],
2264        );
2265        fixture.spawn(
2266            Vec3::ZERO,
2267            FieldKind::ExactDistance,
2268            FieldOp::Union,
2269            &[blend, decoy],
2270        );
2271        fixture.compile().expect("compile");
2272
2273        let program = fixture.domain.field_program();
2274        let probe = [0.0, 4.0, 0.0, 0.0];
2275        let unculled = program.evaluate(probe).expect("eval").0;
2276        let culled = program.evaluate_bounded(probe, 0.0).expect("eval").0;
2277        assert!(
2278            (unculled - (17.0f32.sqrt() - 1.75)).abs() < 1e-5,
2279            "the blend value moved: {unculled}"
2280        );
2281        assert_eq!(
2282            culled, unculled,
2283            "the blend bulge reaches {unculled}, outside the unexpanded enclosing ball"
2284        );
2285    }
2286
2287    #[test]
2288    fn a_posed_subtrees_bound_moves_with_its_transform() {
2289        let mut fixture = Fixture::new();
2290        let leaf = fixture.spawn(Vec3::ZERO, FieldKind::ExactDistance, sphere(0.5), &[]);
2291        let placed = fixture.spawn(
2292            Vec3::new(10.0, 0.0, 0.0),
2293            FieldKind::ExactDistance,
2294            FieldOp::Transform,
2295            &[leaf],
2296        );
2297        let decoy = fixture.spawn(
2298            Vec3::new(-20.0, 0.0, 0.0),
2299            FieldKind::ExactDistance,
2300            sphere(28.0),
2301            &[],
2302        );
2303        fixture.spawn(
2304            Vec3::ZERO,
2305            FieldKind::ExactDistance,
2306            FieldOp::Union,
2307            &[decoy, placed],
2308        );
2309        fixture.compile().expect("compile");
2310
2311        let program = fixture.domain.field_program();
2312        let probe = [10.0, 0.0, 0.0, 0.0];
2313        let culled = program.evaluate_bounded(probe, 0.0).expect("eval").0;
2314        assert!(
2315            (culled + 0.5).abs() < 1e-6,
2316            "the posed sphere reads {culled}, not -0.5, so its bound stayed at the origin"
2317        );
2318    }
2319
2320    #[test]
2321    fn moving_a_shared_leaf_after_stamp_rollover_refreshes_each_dependent_cut_bound() {
2322        let mut fixture = Fixture::new();
2323        let mut compiler = FieldCompiler::new();
2324        let leaf = fixture.spawn(Vec3::ZERO, FieldKind::ExactDistance, sphere(0.5), &[]);
2325        let left = fixture.spawn(
2326            Vec3::new(-100.0, 0.0, 0.0),
2327            FieldKind::ExactDistance,
2328            FieldOp::Transform,
2329            &[leaf],
2330        );
2331        let right = fixture.spawn(
2332            Vec3::new(100.0, 0.0, 0.0),
2333            FieldKind::ExactDistance,
2334            FieldOp::Transform,
2335            &[leaf],
2336        );
2337        let left_decoy = fixture.spawn(
2338            Vec3::new(-120.0, 0.0, 0.0),
2339            FieldKind::ExactDistance,
2340            sphere(28.0),
2341            &[],
2342        );
2343        let right_decoy = fixture.spawn(
2344            Vec3::new(80.0, 0.0, 0.0),
2345            FieldKind::ExactDistance,
2346            sphere(28.0),
2347            &[],
2348        );
2349        let west = fixture.spawn(
2350            Vec3::ZERO,
2351            FieldKind::ExactDistance,
2352            FieldOp::Union,
2353            &[left_decoy, left],
2354        );
2355        let east = fixture.spawn(
2356            Vec3::ZERO,
2357            FieldKind::ExactDistance,
2358            FieldOp::Union,
2359            &[right_decoy, right],
2360        );
2361        fixture.spawn(
2362            Vec3::ZERO,
2363            FieldKind::ExactDistance,
2364            FieldOp::Union,
2365            &[west, east],
2366        );
2367        fixture
2368            .compile_with(&mut compiler)
2369            .expect("initial compile");
2370        fixture.move_to(left, Vec3::new(-99.0, 0.0, 0.0));
2371        fixture
2372            .compile_with(&mut compiler)
2373            .expect("prime parent stamp");
2374        compiler.dirty_stamp = u32::MAX;
2375        fixture.move_to(leaf, Vec3::X * 10.0);
2376        fixture
2377            .compile_with(&mut compiler)
2378            .expect("move shared leaf");
2379
2380        let program = compiler.program();
2381        for point in [[-89.0, 0.0, 0.0, 0.0], [110.0, 0.0, 0.0, 0.0]] {
2382            let value = program.evaluate_bounded(point, 0.0).expect("eval").0;
2383            assert!((value + 0.5).abs() < 1e-6, "{point:?} reads {value}");
2384        }
2385    }
2386
2387    #[test]
2388    fn an_unbounded_subtree_stays_in_the_traversal_and_still_contributes() {
2389        let mut fixture = Fixture::new();
2390        let ball = fixture.spawn(Vec3::ZERO, FieldKind::ExactDistance, sphere(0.5), &[]);
2391        let ground = fixture.spawn(
2392            Vec3::ZERO,
2393            FieldKind::ExactDistance,
2394            FieldOp::HalfSpace {
2395                normal: [0.0, 1.0, 0.0],
2396                offset: 0.0,
2397            },
2398            &[],
2399        );
2400        fixture.spawn(
2401            Vec3::ZERO,
2402            FieldKind::ExactDistance,
2403            FieldOp::Union,
2404            &[ball, ground],
2405        );
2406        fixture.compile().expect("compile");
2407
2408        let program = fixture.domain.field_program();
2409        let probe = [0.0, -3.0, 0.0, 0.0];
2410        let culled = program.evaluate_bounded(probe, 0.0).expect("eval").0;
2411        assert!(
2412            (culled + 3.0).abs() < 1e-6,
2413            "the half-space reads {culled}, not -3.0, so the unbounded subtree was dropped"
2414        );
2415    }
2416
2417    #[test]
2418    fn a_skipped_subtree_never_changes_the_value_the_unculled_program_returns() {
2419        let mut fixture = Fixture::new();
2420        let mut roots = Vec::new();
2421        for i in 0..64 {
2422            let t = i as f32;
2423            roots.push(fixture.spawn(
2424                Vec3::new((t * 0.37).sin() * 40.0, (t * 0.71).cos() * 40.0, t * 0.9),
2425                FieldKind::ExactDistance,
2426                sphere(0.4),
2427                &[],
2428            ));
2429        }
2430        let minuend = fixture.spawn(
2431            Vec3::new(2.0, 0.0, 0.0),
2432            FieldKind::ExactDistance,
2433            sphere(1.0),
2434            &[],
2435        );
2436        let subtrahend = fixture.spawn(
2437            Vec3::new(0.0, 30.0, 0.0),
2438            FieldKind::ExactDistance,
2439            sphere(1.0),
2440            &[],
2441        );
2442        roots.push(fixture.spawn(
2443            Vec3::ZERO,
2444            FieldKind::ExactDistance,
2445            FieldOp::Subtraction,
2446            &[minuend, subtrahend],
2447        ));
2448        let inner = fixture.spawn(
2449            Vec3::new(5.0, 0.0, 0.0),
2450            FieldKind::ExactDistance,
2451            sphere(1.0),
2452            &[],
2453        );
2454        let outer = fixture.spawn(
2455            Vec3::new(5.0, 0.0, 0.0),
2456            FieldKind::ExactDistance,
2457            sphere(3.0),
2458            &[],
2459        );
2460        roots.push(fixture.spawn(
2461            Vec3::ZERO,
2462            FieldKind::ExactDistance,
2463            FieldOp::Intersection,
2464            &[inner, outer],
2465        ));
2466        let mut level = roots;
2467        while level.len() > 1 {
2468            level = level
2469                .chunks(2)
2470                .map(|pair| {
2471                    if pair.len() == 1 {
2472                        pair[0]
2473                    } else {
2474                        fixture.spawn(Vec3::ZERO, FieldKind::ExactDistance, FieldOp::Union, pair)
2475                    }
2476                })
2477                .collect();
2478        }
2479        fixture.compile().expect("compile");
2480
2481        let program = fixture.domain.field_program();
2482        let mut counts = FieldCounts::default();
2483        for i in 0..64 {
2484            let t = i as f32;
2485            let probe = [
2486                (t * 0.29).cos() * 12.0,
2487                (t * 0.53).sin() * 12.0,
2488                t * 0.5 - 8.0,
2489                0.0,
2490            ];
2491            let unculled = program.evaluate(probe).expect("eval").0;
2492            let culled = evaluate_bounded_counted(
2493                &program.program,
2494                &program.primitives,
2495                &program.nodes,
2496                probe,
2497                0.0,
2498                &mut counts,
2499            )
2500            .expect("eval");
2501            assert_eq!(culled, unculled, "probe {probe:?} changed under culling");
2502        }
2503        assert!(
2504            counts.node_skips > 0,
2505            "no subtree was skipped, so the traversal was never exercised"
2506        );
2507    }
2508
2509    #[test]
2510    fn distance_past_the_old_sentinel_matches_a_f64_oracle_with_reported_error() {
2511        use loam_shape::field::DistanceField;
2512
2513        let mut fixture = Fixture::new();
2514        fixture.compile().expect("empty compile");
2515        let empty = fixture.domain.field_program();
2516        assert_eq!(empty.field_kind(), FieldKind::ConservativeBound);
2517        assert_eq!(empty.evaluate([0.0; 4]).unwrap().0, FIELD_FAR);
2518
2519        fixture.spawn(Vec3::ZERO, FieldKind::ExactDistance, sphere(1.0), &[]);
2520        fixture.compile().expect("compile");
2521        let program = fixture.domain.field_program();
2522        let point = [2.0e9, 0.0, 0.0, 0.0];
2523        let unculled = program.evaluate(point).expect("eval").0;
2524        let culled = program.evaluate_bounded(point, 0.0).expect("eval").0;
2525        let oracle = 2_000_000_000.0f64 - 1.0;
2526
2527        assert!(culled > FIELD_FAR);
2528        assert_eq!(culled, unculled);
2529        assert!((culled as f64 - oracle).abs() <= program.error_at(point) as f64);
2530    }
2531
2532    #[test]
2533    fn an_implicit_union_of_exact_spheres_refuses_an_interior_witness() {
2534        use loam_shape::field::DistanceField;
2535
2536        let mut fixture = Fixture::new();
2537        fixture.spawn(-Vec3::X * 0.5, FieldKind::ExactDistance, sphere(1.0), &[]);
2538        fixture.spawn(Vec3::X * 0.5, FieldKind::ExactDistance, sphere(1.0), &[]);
2539        fixture.compile().expect("compile");
2540        let program = fixture.domain.field_program();
2541        assert_eq!(program.field_kind(), FieldKind::ConservativeBound);
2542
2543        let probe = Vec3::new(0.0, 0.2, 0.0);
2544        let bound = program.distance(probe.extend(0.0).to_array());
2545        let distance = 0.2 - 0.75f32.sqrt();
2546        assert!((bound + 0.461_483_5).abs() < 1e-6);
2547        assert!((bound - distance).abs() > FIELD_PROGRAM_ERROR);
2548        let gradient = program.gradient(probe.extend(0.0).to_array());
2549        let normal = Vec3::from_slice(&gradient[..3]).normalize();
2550        let witness = probe - normal * bound;
2551        assert!(
2552            program.distance(witness.extend(0.0).to_array()) < -FIELD_PROGRAM_ERROR,
2553            "the projected witness {witness:?} reached the union boundary"
2554        );
2555
2556        #[cfg(feature = "physics")]
2557        {
2558            use loam_physics::euclidean_r3::sphere_body_r3;
2559            use loam_physics::field_contact::sphere_against_field;
2560            use loam_physics::{FieldRefusal, World};
2561
2562            let mut world = World::new(EuclideanR3);
2563            let body = world.push_body(sphere_body_r3(probe, Vec3::ZERO, 0.1, 1.0).expect("body"));
2564            assert_eq!(
2565                sphere_against_field(
2566                    &world.bodies()[body],
2567                    world.geometry(),
2568                    program,
2569                    &EuclideanR3
2570                )
2571                .err(),
2572                Some(FieldRefusal::Kind(FieldKind::ConservativeBound))
2573            );
2574        }
2575    }
2576
2577    #[test]
2578    fn r4_fields_keep_off_slice_culling_and_refuse_r3_use() {
2579        use crate::view::Vec4;
2580        use loam_math::EuclideanR4;
2581        use loam_shape::field::DistanceField;
2582
2583        for op in [
2584            FieldOp::HyperSphere { radius: 1.0 },
2585            FieldOp::HalfSpace4 {
2586                normal: [1.0, 0.0, 0.0, 1.0],
2587                offset: 0.0,
2588            },
2589        ] {
2590            let mut r3 = Fixture::new();
2591            r3.spawn(Vec3::ZERO, FieldKind::ExactDistance, op, &[]);
2592            assert_eq!(
2593                r3.compile(),
2594                Err(DomainError::Unsupported("field primitive dimension"))
2595            );
2596        }
2597
2598        let mut entities = test_entities();
2599        let mut domain = DomainBuilder::new("r4", EuclideanR4)
2600            .tracked(DEFAULT_LOG_CAPACITY)
2601            .fields()
2602            .build(DomainId::new(0), entities.scene());
2603        let mut place = |at: Vec4, op: FieldOp, operands: &[Entity]| {
2604            let entity = entities.spawn();
2605            domain.attach_pose(entity, Pose::at(at)).expect("pose row");
2606            domain
2607                .attach_field(
2608                    entity,
2609                    Field {
2610                        kind: FieldKind::ExactDistance,
2611                        op,
2612                        operands: operands.to_vec(),
2613                    },
2614                )
2615                .expect("field row");
2616            entity
2617        };
2618        let shell = place(Vec4::new(0.0, 0.0, 0.0, 10.0), sphere(1.0), &[]);
2619        let near = place(
2620            Vec4::new(5.0, 0.0, 0.0, 0.0),
2621            FieldOp::HyperSphere { radius: 2.0 },
2622            &[],
2623        );
2624        let root = place(Vec4::ZERO, FieldOp::Union, &[near, shell]);
2625        domain.compile_fields().expect("compile");
2626
2627        let program = domain.field_program();
2628        let probe = [1.0, 0.0, 0.0, 0.0];
2629        let unculled = program.evaluate(probe).expect("eval").0;
2630        let culled = program.evaluate_bounded(probe, 0.0).expect("eval").0;
2631        assert!(
2632            unculled.abs() < 1e-6,
2633            "the unit sphere translated to w = 10 reads {unculled} on its own surface"
2634        );
2635        assert_eq!(
2636            culled, unculled,
2637            "the hierarchy culled a surface the program evaluates"
2638        );
2639
2640        domain.remove_field(root).expect("root field");
2641        domain.remove_field(shell).expect("shell field");
2642        domain
2643            .set_point(near, Vec4::new(0.0, 0.0, 0.0, 1.0))
2644            .expect("hypersphere pose");
2645        domain.compile_fields().expect("compile hypersphere");
2646
2647        let program = domain.field_program();
2648        let point = [1.0, 0.0, 0.0, 0.0];
2649        let r4_distance = program.distance(point);
2650        let r3_slice_distance = 1.0 - 3.0f32.sqrt();
2651        assert_eq!(program.dimension(), 4);
2652        assert_eq!(program.field_kind(), FieldKind::ExactDistance);
2653        assert!((r4_distance - r3_slice_distance).abs() > program.error_at(point));
2654
2655        #[cfg(feature = "physics")]
2656        {
2657            use loam_physics::euclidean_r3::sphere_body_r3;
2658            use loam_physics::field_contact::sphere_against_field;
2659            use loam_physics::{FieldRefusal, World};
2660
2661            let mut world = World::new(EuclideanR3);
2662            let body = world
2663                .push_body(sphere_body_r3(Vec3::X, Vec3::ZERO, 0.1, 1.0).expect("sphere body"));
2664            assert_eq!(
2665                sphere_against_field(
2666                    &world.bodies()[body],
2667                    world.geometry(),
2668                    program,
2669                    &EuclideanR3
2670                )
2671                .err(),
2672                Some(FieldRefusal::Dimension(4))
2673            );
2674        }
2675    }
2676
2677    #[test]
2678    fn an_intersection_of_exact_half_spaces_reports_a_bound_not_a_distance() {
2679        let mut fixture = Fixture::new();
2680        let west = fixture.spawn(
2681            Vec3::ZERO,
2682            FieldKind::ExactDistance,
2683            FieldOp::HalfSpace {
2684                normal: [-1.0, 0.0, 0.0],
2685                offset: 0.0,
2686            },
2687            &[],
2688        );
2689        let south = fixture.spawn(
2690            Vec3::ZERO,
2691            FieldKind::ExactDistance,
2692            FieldOp::HalfSpace {
2693                normal: [0.0, -1.0, 0.0],
2694                offset: 0.0,
2695            },
2696            &[],
2697        );
2698        fixture.spawn(
2699            Vec3::ZERO,
2700            FieldKind::ExactDistance,
2701            FieldOp::Intersection,
2702            &[west, south],
2703        );
2704        fixture.compile().expect("compile");
2705
2706        let program = fixture.domain.field_program();
2707        let corner = [-1.0, -1.0, 0.0, 0.0];
2708        let (value, kind) = program.evaluate(corner).expect("eval");
2709        assert_eq!(kind, FieldKind::ConservativeBound);
2710        assert!(
2711            (value - 1.0).abs() < 1e-6,
2712            "the intersection reads {value}, not the max of the two half-spaces"
2713        );
2714        assert!(
2715            value < 2.0f32.sqrt() - 1e-3,
2716            "{value} is not below the true distance {} to the quadrant",
2717            2.0f32.sqrt()
2718        );
2719
2720        #[cfg(feature = "physics")]
2721        {
2722            use loam_physics::euclidean_r3::sphere_body_r3;
2723            use loam_physics::field_contact::sphere_against_field;
2724            use loam_physics::{FieldRefusal, World};
2725
2726            let mut world = World::new(EuclideanR3);
2727            let body = world.push_body(
2728                sphere_body_r3(Vec3::new(-1.0, -1.0, 0.0), Vec3::ZERO, 1.2, 1.0).expect("body"),
2729            );
2730            assert_eq!(
2731                sphere_against_field(
2732                    &world.bodies()[body],
2733                    world.geometry(),
2734                    program,
2735                    &EuclideanR3
2736                )
2737                .err(),
2738                Some(FieldRefusal::Kind(FieldKind::ConservativeBound)),
2739                "the contact query read a bound as separation"
2740            );
2741        }
2742    }
2743
2744    #[test]
2745    fn a_curved_chart_refuses_to_compile_a_field() {
2746        use loam_math::HyperbolicH3;
2747        let entities = test_entities();
2748        let mut domain = DomainBuilder::new("h3", HyperbolicH3)
2749            .tracked(DEFAULT_LOG_CAPACITY)
2750            .fields()
2751            .build(DomainId::new(0), entities.scene());
2752        assert_eq!(
2753            domain.compile_fields(),
2754            Err(DomainError::Unsupported("curved field chart"))
2755        );
2756    }
2757}