Skip to main content

ass_renderer/collision/
mod.rs

1//! Collision detection for subtitle positioning
2
3mod resolver;
4
5pub use resolver::{CollisionResolver, SmartCollisionResolver};
6
7/// Trait for collision detection strategies
8pub trait CollisionDetector: Send + Sync {
9    /// Check if a bounding box collides with existing elements
10    fn check_collision(&self, bbox: &BoundingBox, existing: &[BoundingBox]) -> bool;
11
12    /// Find a non-colliding position for a bounding box
13    fn find_free_position(
14        &self,
15        bbox: &BoundingBox,
16        existing: &[BoundingBox],
17        bounds: &BoundingBox,
18    ) -> Option<(f32, f32)>;
19}
20
21/// Bounding box for collision detection
22#[derive(Debug, Clone, Copy)]
23pub struct BoundingBox {
24    /// X coordinate of the box
25    pub x: f32,
26    /// Y coordinate of the box
27    pub y: f32,
28    /// Width of the box
29    pub width: f32,
30    /// Height of the box
31    pub height: f32,
32}
33
34impl BoundingBox {
35    /// Create a new bounding box
36    pub fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
37        Self {
38            x,
39            y,
40            width,
41            height,
42        }
43    }
44
45    /// Check if this box intersects with another
46    pub fn intersects(&self, other: &BoundingBox) -> bool {
47        self.x < other.x + other.width
48            && self.x + self.width > other.x
49            && self.y < other.y + other.height
50            && self.y + self.height > other.y
51    }
52
53    /// Calculate the overlap area with another box
54    pub fn overlap_area(&self, other: &BoundingBox) -> f32 {
55        let x_overlap = (self.x + self.width).min(other.x + other.width) - self.x.max(other.x);
56        let y_overlap = (self.y + self.height).min(other.y + other.height) - self.y.max(other.y);
57
58        if x_overlap > 0.0 && y_overlap > 0.0 {
59            x_overlap * y_overlap
60        } else {
61            0.0
62        }
63    }
64
65    /// Expand the box by a margin
66    pub fn expand(&self, margin: f32) -> Self {
67        Self {
68            x: self.x - margin,
69            y: self.y - margin,
70            width: self.width + margin * 2.0,
71            height: self.height + margin * 2.0,
72        }
73    }
74
75    /// Get the center point
76    pub fn center(&self) -> (f32, f32) {
77        (self.x + self.width / 2.0, self.y + self.height / 2.0)
78    }
79}
80
81/// Positioned subtitle event
82#[derive(Debug, Clone)]
83pub struct PositionedEvent {
84    /// Bounding box of the event
85    pub bbox: BoundingBox,
86    /// Layer number for z-order
87    pub layer: i32,
88    /// Vertical margin
89    pub margin_v: i32,
90    /// Left margin
91    pub margin_l: i32,
92    /// Right margin
93    pub margin_r: i32,
94    /// Text alignment (1-9 numpad style)
95    pub alignment: u8,
96    /// Priority for collision resolution (lower priority events can be moved)
97    pub priority: i32,
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn test_bounding_box_intersection() {
106        let box1 = BoundingBox::new(0.0, 0.0, 100.0, 50.0);
107        let box2 = BoundingBox::new(50.0, 25.0, 100.0, 50.0);
108        let box3 = BoundingBox::new(200.0, 200.0, 50.0, 50.0);
109
110        assert!(box1.intersects(&box2));
111        assert!(box2.intersects(&box1));
112        assert!(!box1.intersects(&box3));
113        assert!(!box3.intersects(&box1));
114    }
115}