ue_types/
bounds.rs

1//! Bounding volume and geometric utility types
2
3use crate::vector::*;
4use crate::transform::*;
5use crate::BinarySerializable;
6use serde::{Deserialize, Serialize};
7use std::fmt;
8
9/// Axis-Aligned Bounding Box (AABB)
10/// 
11/// Represents a 3D bounding box aligned with the coordinate axes.
12/// Commonly used for collision detection and spatial partitioning.
13#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
14pub struct BoundingBox {
15    /// Minimum corner of the box
16    pub min: Vector,
17    /// Maximum corner of the box
18    pub max: Vector,
19}
20
21impl fmt::Display for BoundingBox {
22    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23        let center = self.center();
24        let size = self.size();
25        write!(
26            f,
27            "BoundingBox(Min: ({:.2}, {:.2}, {:.2}), Max: ({:.2}, {:.2}, {:.2}), Center: ({:.2}, {:.2}, {:.2}), Size: ({:.2}, {:.2}, {:.2}))",
28            self.min.x, self.min.y, self.min.z,
29            self.max.x, self.max.y, self.max.z,
30            center.x, center.y, center.z,
31            size.x, size.y, size.z
32        )
33    }
34}
35
36impl BinarySerializable for BoundingBox {}
37
38impl BoundingBox {
39    /// Empty bounding box (inverted min/max for initialization)
40    pub const EMPTY: Self = Self {
41        min: Vector::new(f32::INFINITY, f32::INFINITY, f32::INFINITY),
42        max: Vector::new(f32::NEG_INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY),
43    };
44
45    /// Create a new bounding box with the given min and max corners
46    pub fn new(min: Vector, max: Vector) -> Self {
47        Self { min, max }
48    }
49
50    /// Create a bounding box from center and extent (half-size)
51    pub fn from_center_and_extent(center: Vector, extent: Vector) -> Self {
52        Self {
53            min: center - extent,
54            max: center + extent,
55        }
56    }
57
58    /// Create a bounding box from a single point
59    pub fn from_point(point: Vector) -> Self {
60        Self {
61            min: point,
62            max: point,
63        }
64    }
65
66    /// Create a bounding box that encompasses all given points
67    pub fn from_points(points: &[Vector]) -> Self {
68        if points.is_empty() {
69            return Self::EMPTY;
70        }
71
72        let mut bbox = Self::from_point(points[0]);
73        for &point in &points[1..] {
74            bbox = bbox.expand_to_include(point);
75        }
76        bbox
77    }
78
79    /// Get the center point of the bounding box
80    pub fn center(self) -> Vector {
81        (self.min + self.max) * 0.5
82    }
83
84    /// Get the extent (half-size) of the bounding box
85    pub fn extent(self) -> Vector {
86        (self.max - self.min) * 0.5
87    }
88
89    /// Get the size (full dimensions) of the bounding box
90    pub fn size(self) -> Vector {
91        self.max - self.min
92    }
93
94    /// Get the volume of the bounding box
95    pub fn volume(self) -> f32 {
96        let size = self.size();
97        size.x * size.y * size.z
98    }
99
100    /// Get the surface area of the bounding box
101    pub fn surface_area(self) -> f32 {
102        let size = self.size();
103        2.0 * (size.x * size.y + size.y * size.z + size.z * size.x)
104    }
105
106    /// Check if the bounding box is valid (min <= max for all axes)
107    pub fn is_valid(self) -> bool {
108        self.min.x <= self.max.x && self.min.y <= self.max.y && self.min.z <= self.max.z
109    }
110
111    /// Check if the bounding box is empty (has zero or negative volume)
112    pub fn is_empty(self) -> bool {
113        self.min.x >= self.max.x || self.min.y >= self.max.y || self.min.z >= self.max.z
114    }
115
116    /// Check if a point is inside the bounding box
117    pub fn contains_point(self, point: Vector) -> bool {
118        point.x >= self.min.x && point.x <= self.max.x
119            && point.y >= self.min.y && point.y <= self.max.y
120            && point.z >= self.min.z && point.z <= self.max.z
121    }
122
123    /// Check if another bounding box is completely inside this one
124    pub fn contains_box(self, other: BoundingBox) -> bool {
125        self.contains_point(other.min) && self.contains_point(other.max)
126    }
127
128    /// Check if this bounding box intersects with another
129    pub fn intersects(self, other: BoundingBox) -> bool {
130        self.min.x <= other.max.x && self.max.x >= other.min.x
131            && self.min.y <= other.max.y && self.max.y >= other.min.y
132            && self.min.z <= other.max.z && self.max.z >= other.min.z
133    }
134
135    /// Expand the bounding box to include a point
136    pub fn expand_to_include(self, point: Vector) -> Self {
137        Self {
138            min: self.min.min(point),
139            max: self.max.max(point),
140        }
141    }
142
143    /// Expand the bounding box to include another bounding box
144    pub fn expand_to_include_box(self, other: BoundingBox) -> Self {
145        if other.is_empty() {
146            return self;
147        }
148        if self.is_empty() {
149            return other;
150        }
151        
152        Self {
153            min: self.min.min(other.min),
154            max: self.max.max(other.max),
155        }
156    }
157
158    /// Expand the bounding box by a given amount in all directions
159    pub fn expand_by(self, amount: f32) -> Self {
160        let expansion = Vector::splat(amount);
161        Self {
162            min: self.min - expansion,
163            max: self.max + expansion,
164        }
165    }
166
167    /// Get the intersection of two bounding boxes
168    pub fn intersection(self, other: BoundingBox) -> Self {
169        if !self.intersects(other) {
170            return Self::EMPTY;
171        }
172
173        Self {
174            min: self.min.max(other.min),
175            max: self.max.min(other.max),
176        }
177    }
178
179    /// Transform the bounding box by the given transform
180    pub fn transform(self, transform: Transform) -> Self {
181        if self.is_empty() {
182            return Self::EMPTY;
183        }
184
185        // Transform all 8 corners of the box
186        let corners = [
187            Vector::new(self.min.x, self.min.y, self.min.z),
188            Vector::new(self.max.x, self.min.y, self.min.z),
189            Vector::new(self.min.x, self.max.y, self.min.z),
190            Vector::new(self.max.x, self.max.y, self.min.z),
191            Vector::new(self.min.x, self.min.y, self.max.z),
192            Vector::new(self.max.x, self.min.y, self.max.z),
193            Vector::new(self.min.x, self.max.y, self.max.z),
194            Vector::new(self.max.x, self.max.y, self.max.z),
195        ];
196
197        let transformed_corners: Vec<Vector> = corners
198            .iter()
199            .map(|&corner| transform.transform_point(corner))
200            .collect();
201
202        Self::from_points(&transformed_corners)
203    }
204
205    /// Get the distance from a point to the bounding box (0 if inside)
206    pub fn distance_to_point(self, point: Vector) -> f32 {
207        let closest = point.clamp(self.min, self.max);
208        (point - closest).length()
209    }
210
211    /// Get the closest point on the bounding box to a given point
212    pub fn closest_point_to(self, point: Vector) -> Vector {
213        point.clamp(self.min, self.max)
214    }
215}
216
217/// Bounding Sphere
218/// 
219/// Represents a 3D sphere defined by center and radius.
220/// Often used for fast collision detection and culling.
221#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
222pub struct BoundingSphere {
223    /// Center of the sphere
224    pub center: Vector,
225    /// Radius of the sphere
226    pub radius: f32,
227}
228
229impl fmt::Display for BoundingSphere {
230    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231        write!(
232            f,
233            "BoundingSphere(Center: ({:.2}, {:.2}, {:.2}), Radius: {:.2})",
234            self.center.x, self.center.y, self.center.z, self.radius
235        )
236    }
237}
238
239impl BinarySerializable for BoundingSphere {}
240
241impl BoundingSphere {
242    /// Create a new bounding sphere
243    pub fn new(center: Vector, radius: f32) -> Self {
244        Self { center, radius }
245    }
246
247    /// Create a bounding sphere from a bounding box
248    pub fn from_box(bbox: BoundingBox) -> Self {
249        let center = bbox.center();
250        let radius = (bbox.max - center).length();
251        Self { center, radius }
252    }
253
254    /// Create a bounding sphere that encompasses all given points
255    pub fn from_points(points: &[Vector]) -> Self {
256        if points.is_empty() {
257            return Self::new(Vector::ZERO, 0.0);
258        }
259
260        // Simple implementation: use bounding box center and max distance
261        let bbox = BoundingBox::from_points(points);
262        let center = bbox.center();
263        
264        let radius = points
265            .iter()
266            .map(|&point| (point - center).length())
267            .fold(0.0f32, f32::max);
268
269        Self { center, radius }
270    }
271
272    /// Check if a point is inside the sphere
273    pub fn contains_point(self, point: Vector) -> bool {
274        (point - self.center).length_squared() <= self.radius * self.radius
275    }
276
277    /// Check if another sphere is completely inside this one
278    pub fn contains_sphere(self, other: BoundingSphere) -> bool {
279        let distance = (other.center - self.center).length();
280        distance + other.radius <= self.radius
281    }
282
283    /// Check if this sphere intersects with another sphere
284    pub fn intersects_sphere(self, other: BoundingSphere) -> bool {
285        let distance_squared = (other.center - self.center).length_squared();
286        let radii_sum = self.radius + other.radius;
287        distance_squared <= radii_sum * radii_sum
288    }
289
290    /// Check if this sphere intersects with a bounding box
291    pub fn intersects_box(self, bbox: BoundingBox) -> bool {
292        let closest_point = bbox.closest_point_to(self.center);
293        self.contains_point(closest_point)
294    }
295
296    /// Transform the bounding sphere by the given transform
297    pub fn transform(self, transform: Transform) -> Self {
298        let new_center = transform.transform_point(self.center);
299        
300        // Calculate the maximum scale factor to determine new radius
301        let scale_x = transform.scale.x.abs();
302        let scale_y = transform.scale.y.abs();
303        let scale_z = transform.scale.z.abs();
304        let max_scale = scale_x.max(scale_y).max(scale_z);
305        
306        Self {
307            center: new_center,
308            radius: self.radius * max_scale,
309        }
310    }
311
312    /// Get the distance from a point to the sphere surface (negative if inside)
313    pub fn distance_to_point(self, point: Vector) -> f32 {
314        (point - self.center).length() - self.radius
315    }
316
317    /// Expand the sphere to include a point
318    pub fn expand_to_include(self, point: Vector) -> Self {
319        let distance = (point - self.center).length();
320        if distance <= self.radius {
321            return self;
322        }
323
324        Self {
325            center: self.center,
326            radius: distance,
327        }
328    }
329
330    /// Expand the sphere to include another sphere
331    pub fn expand_to_include_sphere(self, other: BoundingSphere) -> Self {
332        let distance = (other.center - self.center).length();
333        let new_radius = (distance + other.radius).max(self.radius);
334        
335        Self {
336            center: self.center,
337            radius: new_radius,
338        }
339    }
340}
341
342#[cfg(test)]
343mod tests {
344    use super::*;
345
346    #[test]
347    fn test_bounding_box_creation() {
348        let bbox = BoundingBox::new(
349            Vector::new(-1.0, -1.0, -1.0),
350            Vector::new(1.0, 1.0, 1.0)
351        );
352        
353        assert_eq!(bbox.center(), Vector::ZERO);
354        assert_eq!(bbox.extent(), Vector::ONE);
355        assert_eq!(bbox.size(), Vector::splat(2.0));
356    }
357
358    #[test]
359    fn test_bounding_box_contains() {
360        let bbox = BoundingBox::new(
361            Vector::new(-1.0, -1.0, -1.0),
362            Vector::new(1.0, 1.0, 1.0)
363        );
364        
365        assert!(bbox.contains_point(Vector::ZERO));
366        assert!(bbox.contains_point(Vector::new(0.5, 0.5, 0.5)));
367        assert!(!bbox.contains_point(Vector::new(2.0, 0.0, 0.0)));
368    }
369
370    #[test]
371    fn test_bounding_box_intersection() {
372        let bbox1 = BoundingBox::new(
373            Vector::new(-1.0, -1.0, -1.0),
374            Vector::new(1.0, 1.0, 1.0)
375        );
376        let bbox2 = BoundingBox::new(
377            Vector::new(0.0, 0.0, 0.0),
378            Vector::new(2.0, 2.0, 2.0)
379        );
380        
381        assert!(bbox1.intersects(bbox2));
382        
383        let intersection = bbox1.intersection(bbox2);
384        assert_eq!(intersection.min, Vector::ZERO);
385        assert_eq!(intersection.max, Vector::ONE);
386    }
387
388    #[test]
389    fn test_bounding_sphere_creation() {
390        let sphere = BoundingSphere::new(Vector::ZERO, 1.0);
391        
392        assert!(sphere.contains_point(Vector::new(0.5, 0.0, 0.0)));
393        assert!(!sphere.contains_point(Vector::new(2.0, 0.0, 0.0)));
394    }
395
396    #[test]
397    fn test_sphere_box_intersection() {
398        let sphere = BoundingSphere::new(Vector::ZERO, 1.0);
399        let bbox = BoundingBox::new(
400            Vector::new(0.5, -0.5, -0.5),
401            Vector::new(1.5, 0.5, 0.5)
402        );
403        
404        assert!(sphere.intersects_box(bbox));
405    }
406
407    #[test]
408    fn test_bounding_box_display() {
409        let bbox = BoundingBox::new(
410            Vector::new(-1.0, -2.0, -3.0),
411            Vector::new(1.0, 2.0, 3.0)
412        );
413        
414        let display_str = format!("{}", bbox);
415        assert!(display_str.contains("Min: (-1.00, -2.00, -3.00)"));
416        assert!(display_str.contains("Max: (1.00, 2.00, 3.00)"));
417        assert!(display_str.contains("Center: (0.00, 0.00, 0.00)"));
418        assert!(display_str.contains("Size: (2.00, 4.00, 6.00)"));
419    }
420
421    #[test]
422    fn test_bounding_sphere_display() {
423        let sphere = BoundingSphere::new(Vector::new(1.0, 2.0, 3.0), 5.0);
424        
425        let display_str = format!("{}", sphere);
426        assert!(display_str.contains("Center: (1.00, 2.00, 3.00)"));
427        assert!(display_str.contains("Radius: 5.00"));
428    }
429
430    #[test]
431    fn test_bounding_box_json_serialization() {
432        let bbox = BoundingBox::new(
433            Vector::new(-1.0, -2.0, -3.0),
434            Vector::new(1.0, 2.0, 3.0)
435        );
436        
437        // Test JSON serialization
438        let json = serde_json::to_string(&bbox).unwrap();
439        let deserialized: BoundingBox = serde_json::from_str(&json).unwrap();
440        
441        assert_eq!(bbox, deserialized);
442    }
443
444    #[test]
445    fn test_bounding_sphere_json_serialization() {
446        let sphere = BoundingSphere::new(Vector::new(1.0, 2.0, 3.0), 5.0);
447        
448        // Test JSON serialization
449        let json = serde_json::to_string(&sphere).unwrap();
450        let deserialized: BoundingSphere = serde_json::from_str(&json).unwrap();
451        
452        assert_eq!(sphere, deserialized);
453    }
454
455    #[test]
456    fn test_bounding_box_binary_serialization() {
457        let bbox = BoundingBox::new(
458            Vector::new(-1.0, -2.0, -3.0),
459            Vector::new(1.0, 2.0, 3.0)
460        );
461        
462        // Test binary serialization
463        let binary = bbox.to_binary().unwrap();
464        let deserialized = BoundingBox::from_binary(&binary).unwrap();
465        
466        assert_eq!(bbox, deserialized);
467    }
468
469    #[test]
470    fn test_bounding_sphere_binary_serialization() {
471        let sphere = BoundingSphere::new(Vector::new(1.0, 2.0, 3.0), 5.0);
472        
473        // Test binary serialization
474        let binary = sphere.to_binary().unwrap();
475        let deserialized = BoundingSphere::from_binary(&binary).unwrap();
476        
477        assert_eq!(sphere, deserialized);
478    }
479}