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}