Skip to main content

oxiphysics_gpu/bvh/
types.rs

1// Copyright 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3
4//! Core BVH types: AABB, primitives, nodes, flat nodes, and ray-hit results.
5
6// ============================================================================
7// Aabb
8// ============================================================================
9
10/// An axis-aligned bounding box stored as two `[f32; 3]` corners.
11#[derive(Debug, Clone, PartialEq)]
12pub struct Aabb {
13    /// Minimum corner (component-wise).
14    pub min: [f32; 3],
15    /// Maximum corner (component-wise).
16    pub max: [f32; 3],
17}
18
19impl Aabb {
20    /// Construct an `Aabb` from explicit min/max corners.
21    pub fn new(min: [f32; 3], max: [f32; 3]) -> Self {
22        Self { min, max }
23    }
24
25    /// Construct a degenerate `Aabb` that contains exactly one point.
26    pub fn point(p: [f32; 3]) -> Self {
27        Self { min: p, max: p }
28    }
29
30    /// Return the smallest `Aabb` that contains both `a` and `b`.
31    pub fn merge(a: &Aabb, b: &Aabb) -> Aabb {
32        Aabb {
33            min: [
34                a.min[0].min(b.min[0]),
35                a.min[1].min(b.min[1]),
36                a.min[2].min(b.min[2]),
37            ],
38            max: [
39                a.max[0].max(b.max[0]),
40                a.max[1].max(b.max[1]),
41                a.max[2].max(b.max[2]),
42            ],
43        }
44    }
45
46    /// Returns `true` if this box overlaps `other` (touching counts).
47    pub fn intersects(&self, other: &Aabb) -> bool {
48        self.min[0] <= other.max[0]
49            && self.max[0] >= other.min[0]
50            && self.min[1] <= other.max[1]
51            && self.max[1] >= other.min[1]
52            && self.min[2] <= other.max[2]
53            && self.max[2] >= other.min[2]
54    }
55
56    /// Returns `true` if point `p` lies inside or on the surface of this box.
57    pub fn contains(&self, p: [f32; 3]) -> bool {
58        p[0] >= self.min[0]
59            && p[0] <= self.max[0]
60            && p[1] >= self.min[1]
61            && p[1] <= self.max[1]
62            && p[2] >= self.min[2]
63            && p[2] <= self.max[2]
64    }
65
66    /// Surface area of the box (sum of all six face areas).
67    pub fn surface_area(&self) -> f32 {
68        let dx = self.max[0] - self.min[0];
69        let dy = self.max[1] - self.min[1];
70        let dz = self.max[2] - self.min[2];
71        2.0 * (dx * dy + dy * dz + dz * dx)
72    }
73
74    /// Geometric centre of the box.
75    pub fn center(&self) -> [f32; 3] {
76        [
77            0.5 * (self.min[0] + self.max[0]),
78            0.5 * (self.min[1] + self.max[1]),
79            0.5 * (self.min[2] + self.max[2]),
80        ]
81    }
82
83    /// Return a copy of this box expanded uniformly by `margin` on every side.
84    pub fn expand(&self, margin: f32) -> Aabb {
85        Aabb {
86            min: [
87                self.min[0] - margin,
88                self.min[1] - margin,
89                self.min[2] - margin,
90            ],
91            max: [
92                self.max[0] + margin,
93                self.max[1] + margin,
94                self.max[2] + margin,
95            ],
96        }
97    }
98}
99
100// ============================================================================
101// BvhPrimitive
102// ============================================================================
103
104/// A leaf primitive: an AABB together with the logical object it belongs to.
105#[derive(Debug, Clone)]
106pub struct BvhPrimitive {
107    /// Bounding box of the primitive.
108    pub aabb: Aabb,
109    /// Caller-defined identifier (returned by queries).
110    pub object_id: usize,
111}
112
113impl BvhPrimitive {
114    /// Construct a `BvhPrimitive`.
115    pub fn new(aabb: Aabb, object_id: usize) -> Self {
116        Self { aabb, object_id }
117    }
118}
119
120// ============================================================================
121// BvhNode
122// ============================================================================
123
124/// A node in the recursive BVH tree.
125#[derive(Debug)]
126pub struct BvhNode {
127    /// Bounding box that contains all children / primitives.
128    pub aabb: Aabb,
129    /// Left subtree (internal nodes only).
130    pub left: Option<Box<BvhNode>>,
131    /// Right subtree (internal nodes only).
132    pub right: Option<Box<BvhNode>>,
133    /// Indices into `Bvh::primitives` (leaf nodes only).
134    pub primitives: Vec<usize>,
135}
136
137impl BvhNode {
138    /// Returns `true` if this node is a leaf (holds primitives directly).
139    pub fn is_leaf(&self) -> bool {
140        self.left.is_none() && self.right.is_none()
141    }
142}
143
144// ============================================================================
145// Flat BVH node
146// ============================================================================
147
148/// A single node in the linearised (flat) BVH representation.
149///
150/// Layout:
151/// * If `count == 0` this is an **internal** node.
152///   - The **left** child is always at index `node_idx + 1` (i.e. stored
153///     immediately after the parent in DFS pre-order).
154///   - `left_first` holds the index of the **right** child.
155/// * If `count > 0` this is a **leaf**; `left_first` is the start index into
156///   the accompanying primitive-index slice and `count` is the number of
157///   entries.
158#[derive(Debug, Clone)]
159pub struct FlatBvhNode {
160    /// Bounding box of this node.
161    pub aabb: Aabb,
162    /// Right-child index (internal) or first-primitive index (leaf).
163    pub left_first: u32,
164    /// 0 for internal nodes; number of primitives for leaf nodes.
165    pub count: u32,
166}
167
168// ============================================================================
169// RayHit
170// ============================================================================
171
172/// Result of a closest-hit ray traversal.
173#[derive(Debug, Clone)]
174pub struct RayHit {
175    /// Object ID of the closest hit primitive.
176    pub object_id: usize,
177    /// Ray parameter at which the hit occurred.
178    pub t: f32,
179}
180
181// ============================================================================
182// GpuRay
183// ============================================================================
184
185/// A ray for GPU BVH traversal.
186#[derive(Debug, Clone, Copy, PartialEq)]
187pub struct GpuRay {
188    /// Ray origin.
189    pub origin: [f32; 3],
190    /// Ray direction (does not need to be normalised).
191    pub direction: [f32; 3],
192    /// Maximum ray parameter (hit farther than this is ignored).
193    pub max_t: f32,
194}
195
196impl GpuRay {
197    /// Construct a new ray.
198    pub fn new(origin: [f32; 3], direction: [f32; 3], max_t: f32) -> Self {
199        Self {
200            origin,
201            direction,
202            max_t,
203        }
204    }
205}
206
207// ============================================================================
208// BVH statistics types
209// ============================================================================
210
211/// Runtime statistics about a BVH tree.
212#[derive(Debug, Clone)]
213pub struct BvhStats {
214    /// Total number of nodes (internal + leaf).
215    pub node_count: usize,
216    /// Number of leaf nodes.
217    pub leaf_count: usize,
218    /// Number of internal nodes.
219    pub internal_count: usize,
220    /// Maximum tree depth.
221    pub max_depth: usize,
222    /// Total number of primitives stored across all leaves.
223    pub total_primitives: usize,
224    /// Average primitives per leaf.
225    pub avg_primitives_per_leaf: f32,
226}
227
228/// Extended BVH tree statistics including average fan-out.
229#[derive(Debug, Clone)]
230pub struct BvhTreeStatistics {
231    /// Total node count.
232    pub node_count: usize,
233    /// Number of leaf nodes.
234    pub leaf_count: usize,
235    /// Number of internal nodes.
236    pub internal_count: usize,
237    /// Maximum depth from root (1-indexed).
238    pub max_depth: usize,
239    /// Total number of primitives across all leaves.
240    pub total_primitives: usize,
241    /// Average number of children per internal node (fan-out).
242    /// For a binary tree this is at most 2.
243    pub avg_fanout: f32,
244    /// Total surface area of all leaf AABBs.
245    pub total_leaf_surface_area: f32,
246}
247
248// ============================================================================
249// Morton / LBVH types
250// ============================================================================
251
252/// LBVH primitive: AABB + Morton code.
253#[derive(Debug, Clone)]
254pub struct LbvhPrimitive {
255    /// Bounding box.
256    pub aabb: Aabb,
257    /// Caller-defined object ID.
258    pub object_id: usize,
259    /// 30-bit Morton code computed from the AABB centroid.
260    pub morton: u32,
261}
262
263/// A cluster of Morton-coded primitives, with a pre-computed bounding radius.
264#[derive(Debug, Clone)]
265pub struct MortonCluster {
266    /// Indices of the primitives in this cluster (into the parent slice).
267    pub indices: Vec<usize>,
268    /// Axis-aligned bounding box of the cluster.
269    pub aabb: Aabb,
270    /// Bounding sphere radius (centred at the AABB centre).
271    pub radius: f32,
272}