1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
//! Metric ball — the unit ball of a norm, as a primitive.
//!
//! Every other primitive in this crate is written against the Euclidean norm:
//! `‖p‖₂ − r` is a sphere. Write the same expression with a different norm
//! and it is a different shape — `‖p‖∞ − r` is a cube, `‖p‖₁ − r` an
//! octahedron — while the *expression* has not changed at all. This
//! primitive makes the norm a parameter, so the shape is chosen by three
//! weights rather than by picking a different variant.
//!
//! The norm is a non-negative combination of the three bases,
//! `g(p) = w₁‖p‖₁ + w₂‖p‖₂ + w∞‖p‖∞`, which is a metric for exactly the
//! non-negative weights (a negative one dents the unit ball inwards and the
//! triangle inequality fails). [`MetricWeights`] enforces that at
//! construction, so this function never has to.
//!
//! # What the field is worth
//!
//! `g(p) − r` is a true distance in *its own* metric, and in Euclidean terms
//! its gradient is bounded by [`MetricWeights::lipschitz`] — exactly, not
//! approximately. For the cube and the sphere that bound is `1` and the
//! field never over-reports; for the octahedron it is `√3`, so a marcher has
//! to divide its step by that (which [`eval_lipschitz`](crate::interval::eval_lipschitz)
//! reports and `RaymarchConfig` already divides by). The tight axis-aligned
//! box of the ball is a *different* number, [`MetricWeights::axis_extent`].
use ;
use Vec3;
/// SDF of the ball `{p : g(p) ≤ radius}` for the weighted norm `g`.
///
/// The weights are taken as three scalars rather than a
/// [`MetricWeights`](alice_det_math::metric::MetricWeights) so the node stays
/// a plain bag of `f32` (serialisable, FFI-able, JIT-encodable like every
/// other primitive). They are validated where the node is built —
/// [`SdfNode::metric_ball`](crate::types::SdfNode::metric_ball) only accepts
/// a `MetricWeights` — so no validation happens per sample.
///
/// The summation order is the same left-to-right order as
/// `MetricWeights::norm`, and `tests/test_metric_ball_oracle.rs` asserts the
/// two agree bit for bit; a reordering here would be a silent divergence
/// between this primitive and every other consumer of that law.