Skip to main content

alice_sdf/types/
containers.rs

1//! Container types: SdfTree, SdfMetadata, Aabb, Ray, Hit
2//!
3//! Author: Moroya Sakamoto
4
5use crate::compiled::real::LawMinMax;
6use glam::Vec3;
7use serde::{Deserialize, Serialize};
8
9use super::SdfNode;
10
11/// SDF Tree - top-level container
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct SdfTree {
14    /// Version string
15    pub version: String,
16    /// Root node
17    pub root: SdfNode,
18    /// Optional metadata
19    pub metadata: Option<SdfMetadata>,
20}
21
22impl SdfTree {
23    /// Create a new SDF tree
24    pub fn new(root: SdfNode) -> Self {
25        Self {
26            version: env!("CARGO_PKG_VERSION").to_string(),
27            root,
28            metadata: None,
29        }
30    }
31
32    /// Create with metadata
33    pub fn with_metadata(root: SdfNode, metadata: SdfMetadata) -> Self {
34        Self {
35            version: env!("CARGO_PKG_VERSION").to_string(),
36            root,
37            metadata: Some(metadata),
38        }
39    }
40
41    /// Get total node count
42    pub fn node_count(&self) -> u32 {
43        self.root.node_count()
44    }
45}
46
47/// Optional metadata for SDF trees
48#[derive(Debug, Clone, Serialize, Deserialize, Default)]
49pub struct SdfMetadata {
50    /// Name of the model
51    pub name: Option<String>,
52    /// Description
53    pub description: Option<String>,
54    /// Author
55    pub author: Option<String>,
56    /// Bounding box hint (min, max)
57    pub bounds: Option<(Vec3, Vec3)>,
58    /// Custom key-value pairs
59    pub custom: Option<std::collections::HashMap<String, String>>,
60}
61
62/// Axis-aligned bounding box
63#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
64pub struct Aabb {
65    /// Minimum corner
66    pub min: Vec3,
67    /// Maximum corner
68    pub max: Vec3,
69}
70
71impl Aabb {
72    /// Create a new AABB
73    #[inline]
74    pub const fn new(min: Vec3, max: Vec3) -> Self {
75        Self { min, max }
76    }
77
78    /// Create an empty (inverted) AABB: `min = +∞`, `max = −∞`, so that the
79    /// first [`expand_point`](Self::expand_point) makes it the point itself
80    #[inline]
81    pub const fn empty() -> Self {
82        Self {
83            min: Vec3::splat(f32::INFINITY),
84            max: Vec3::splat(f32::NEG_INFINITY),
85        }
86    }
87
88    /// Expand in place to include a point
89    #[inline]
90    pub fn expand_point(&mut self, point: Vec3) {
91        self.min = self.min.min(point);
92        self.max = self.max.max(point);
93    }
94
95    /// Expand in place to include another AABB (the in-place form of
96    /// [`union`](Self::union))
97    #[inline]
98    pub fn expand_aabb(&mut self, other: &Self) {
99        self.min = self.min.min(other.min);
100        self.max = self.max.max(other.max);
101    }
102
103    /// Surface area `2·(dx·dy + dy·dz + dz·dx)` (for SAH)
104    #[inline]
105    pub fn surface_area(&self) -> f32 {
106        let d = self.max - self.min;
107        2.0 * (d.x * d.y + d.y * d.z + d.z * d.x)
108    }
109
110    /// Longest axis (0 = X, 1 = Y, 2 = Z; ties go to the later axis)
111    #[inline]
112    pub fn longest_axis(&self) -> usize {
113        let d = self.max - self.min;
114        if d.x > d.y && d.x > d.z {
115            0
116        } else if d.y > d.z {
117            1
118        } else {
119            2
120        }
121    }
122
123    /// Signed distance to the box (negative inside, positive outside)
124    #[inline]
125    pub fn signed_distance(&self, point: Vec3) -> f32 {
126        let q = (point - self.center()).abs() - (self.max - self.min) * 0.5;
127        q.max(Vec3::ZERO).length() + q.x.law_max(q.y.law_max(q.z)).law_min(0.0)
128    }
129
130    /// Create from center and half-extents
131    pub fn from_center_extents(center: Vec3, half_extents: Vec3) -> Self {
132        Self {
133            min: center - half_extents,
134            max: center + half_extents,
135        }
136    }
137
138    /// Get center point
139    #[inline]
140    pub fn center(&self) -> Vec3 {
141        (self.min + self.max) * 0.5
142    }
143
144    /// Get size
145    pub fn size(&self) -> Vec3 {
146        self.max - self.min
147    }
148
149    /// Get half-extents
150    pub fn half_extents(&self) -> Vec3 {
151        self.size() * 0.5
152    }
153
154    /// Check if point is inside
155    pub fn contains(&self, point: Vec3) -> bool {
156        point.x >= self.min.x
157            && point.x <= self.max.x
158            && point.y >= self.min.y
159            && point.y <= self.max.y
160            && point.z >= self.min.z
161            && point.z <= self.max.z
162    }
163
164    /// Expand to include another AABB
165    pub fn union(&self, other: &Self) -> Self {
166        Self {
167            min: self.min.min(other.min),
168            max: self.max.max(other.max),
169        }
170    }
171}
172
173/// Ray for raycasting
174#[derive(Debug, Clone, Copy)]
175pub struct Ray {
176    /// Ray origin point
177    pub origin: Vec3,
178    /// Ray direction (normalized)
179    pub direction: Vec3,
180}
181
182impl Ray {
183    /// Create a new ray
184    pub fn new(origin: Vec3, direction: Vec3) -> Self {
185        Self {
186            origin,
187            direction: direction.normalize(),
188        }
189    }
190
191    /// Get point along ray at distance t
192    pub fn at(&self, t: f32) -> Vec3 {
193        self.origin + self.direction * t
194    }
195}
196
197/// Hit result from raycasting
198#[derive(Debug, Clone, Copy)]
199pub struct Hit {
200    /// Distance along ray
201    pub distance: f32,
202    /// Hit point
203    pub point: Vec3,
204    /// Surface normal (approximate)
205    pub normal: Vec3,
206    /// Number of marching steps
207    pub steps: u32,
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213
214    #[test]
215    fn test_sdf_tree() {
216        let tree = SdfTree::new(SdfNode::sphere(1.0));
217        assert_eq!(tree.node_count(), 1);
218    }
219
220    #[test]
221    fn test_aabb() {
222        let aabb = Aabb::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0));
223        assert!(aabb.contains(Vec3::ZERO));
224        assert!(!aabb.contains(Vec3::new(2.0, 0.0, 0.0)));
225    }
226}