ifc_lite_clash/aabb.rs
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Axis-aligned bounding boxes and the cheap separation/overlap helpers the
6//! clash engine relies on.
7//!
8//! The box math lives in the Plato-generated `generated::plato::Box3` (from
9//! `tools/plato/clash_math.plato`); the [`Aabb`] type below keeps the crate's
10//! `[f64; 3]` corner shape and the buffer-walking [`Aabb::from_positions`], and
11//! its remaining methods/free functions are byte-compatible adapters over
12//! `Box3`.
13
14use crate::generated::plato::{Box3, Vec3 as PlatoVec3};
15use crate::vec3::Vec3;
16
17/// An axis-aligned bounding box with explicit `min`/`max` corners.
18#[derive(Clone, Copy, PartialEq, Debug)]
19pub struct Aabb {
20 pub min: [f64; 3],
21 pub max: [f64; 3],
22}
23
24/// Pack an [`Aabb`] into the generated `Box3`.
25#[inline]
26fn to_box3(a: &Aabb) -> Box3 {
27 Box3::new(
28 PlatoVec3::new(a.min[0], a.min[1], a.min[2]),
29 PlatoVec3::new(a.max[0], a.max[1], a.max[2]),
30 )
31}
32
33/// Unpack a generated `Box3` back into an [`Aabb`].
34#[inline]
35fn from_box3(b: Box3) -> Aabb {
36 Aabb::new(
37 [b.Min.X, b.Min.Y, b.Min.Z],
38 [b.Max.X, b.Max.Y, b.Max.Z],
39 )
40}
41
42impl Aabb {
43 #[inline]
44 pub fn new(min: [f64; 3], max: [f64; 3]) -> Self {
45 Self { min, max }
46 }
47
48 /// Axis-aligned bounds of a packed `[x, y, z, ...]` position buffer.
49 ///
50 /// Mirrors `fromPositions`; the optional transform from the TS source is
51 /// dropped because the native API ingests world-space geometry directly.
52 /// Stays a hand-written buffer walk (the generated math is per-box, not
53 /// over a flat vertex stream).
54 pub fn from_positions(positions: &[f64]) -> Aabb {
55 if positions.len() < 3 {
56 return Aabb::new([0.0, 0.0, 0.0], [0.0, 0.0, 0.0]);
57 }
58 let mut min = [f64::INFINITY; 3];
59 let mut max = [f64::NEG_INFINITY; 3];
60 let mut i = 0;
61 while i + 2 < positions.len() {
62 for axis in 0..3 {
63 let v = positions[i + axis];
64 if v < min[axis] {
65 min[axis] = v;
66 }
67 if v > max[axis] {
68 max[axis] = v;
69 }
70 }
71 i += 3;
72 }
73 Aabb::new(min, max)
74 }
75
76 /// Expand bounds by `m` on every side. Delegates to `Box3::Inflate`.
77 #[inline]
78 pub fn inflate(&self, m: f64) -> Aabb {
79 from_box3(to_box3(self).Inflate(m))
80 }
81
82 /// Center point of the box. Delegates to `Box3::Center`.
83 #[inline]
84 pub fn center(&self) -> Vec3 {
85 let c = to_box3(self).Center();
86 [c.X, c.Y, c.Z]
87 }
88
89 /// True when the two boxes overlap (touching counts). Delegates to
90 /// `Box3::Intersects`.
91 #[inline]
92 pub fn intersects(&self, b: &Aabb) -> bool {
93 to_box3(self).Intersects(to_box3(b))
94 }
95}
96
97/// Signed gap between two boxes: `>0` is the Euclidean separation, `<0` is the
98/// penetration depth (negative of the minimum-axis overlap). Delegates to
99/// `Box3::SignedGap`.
100pub fn signed_gap(a: &Aabb, b: &Aabb) -> f64 {
101 to_box3(a).SignedGap(to_box3(b))
102}
103
104/// The intersection box of two overlapping bounds (clamped to be non-inverted).
105/// Delegates to `Box3::OverlapBounds`.
106pub fn overlap_bounds(a: &Aabb, b: &Aabb) -> Aabb {
107 from_box3(to_box3(a).OverlapBounds(to_box3(b)))
108}
109
110/// Bounds enclosing two points. Delegates to `Vec3::BoundsOfPoints`.
111pub fn bounds_of_points(a: Vec3, b: Vec3) -> Aabb {
112 from_box3(
113 PlatoVec3::new(a[0], a[1], a[2]).BoundsOfPoints(PlatoVec3::new(b[0], b[1], b[2])),
114 )
115}
116
117/// True when `outer` fully contains `inner` (face-sharing counts as contained).
118/// Cheap precondition for the enclosed-solid test in the narrow phase. Delegates
119/// to `Box3::Contains`, which mirrors the TS `aabbContains` exactly (same
120/// `<=`/`>=`, axis order 0,1,2).
121#[inline]
122pub fn aabb_contains(outer: &Aabb, inner: &Aabb) -> bool {
123 to_box3(outer).Contains(to_box3(inner))
124}
125
126/// Precision floor of a depth measured along the UNIT direction `axis`: the
127/// pair's per-axis f32 noise projected onto it (#5405). Adapter over the
128/// generated `Vec3::DepthFloor` — the same noise rule as the tri-tri contact
129/// band.
130pub(crate) fn depth_floor(axis: Vec3, a: &Aabb, b: &Aabb) -> f64 {
131 PlatoVec3::new(axis[0], axis[1], axis[2]).DepthFloor(to_box3(a), to_box3(b))
132}
133
134/// Precision floor of the AABB estimate `-signed_gap(a, b)`: the noise of the
135/// world axis that estimate is measured along. Adapter over the generated
136/// `Box3::EstimateFloor`.
137pub(crate) fn estimate_floor(a: &Aabb, b: &Aabb) -> f64 {
138 to_box3(a).EstimateFloor(to_box3(b))
139}