Skip to main content

ranim_items/vitem/geometry/
anchor.rs

1use ranim_core::{
2    glam::{DAffine3, DVec2, DVec3},
3    prelude::Transformed,
4    traits::Locate,
5};
6
7use crate::vitem::geometry::{Arc, ArcBetweenPoints, Circle, Ellipse, EllipticArc};
8
9/// `Origin` anchor for shapes with an origin point.
10#[derive(Debug, Clone, Copy)]
11pub struct Origin;
12
13/// Focus of an ellipse.
14#[derive(Debug, Clone, Copy)]
15pub enum Focus {
16    /// Focus on the positive semi-major axis.
17    Pos,
18    /// Focus on the negative semi-major axis.
19    Neg,
20}
21
22impl<T, G> Locate<Transformed<T, G>> for Origin
23where
24    Origin: Locate<T>,
25    G: Clone + Into<DAffine3>,
26{
27    fn locate(&self, target: &Transformed<T, G>) -> DVec3 {
28        target
29            .transform
30            .clone()
31            .into()
32            .transform_point3(self.locate(&target.inner))
33    }
34}
35
36impl<T, G> Locate<Transformed<T, G>> for Focus
37where
38    Focus: Locate<T>,
39    G: Clone + Into<DAffine3>,
40{
41    fn locate(&self, target: &Transformed<T, G>) -> DVec3 {
42        target
43            .transform
44            .clone()
45            .into()
46            .transform_point3(self.locate(&target.inner))
47    }
48}
49
50impl Locate<Arc> for Origin {
51    fn locate(&self, _target: &Arc) -> DVec3 {
52        DVec3::ZERO
53    }
54}
55
56impl Locate<Arc> for Focus {
57    fn locate(&self, _target: &Arc) -> DVec3 {
58        DVec3::ZERO
59    }
60}
61
62impl Locate<ArcBetweenPoints> for Origin {
63    fn locate(&self, target: &ArcBetweenPoints) -> DVec3 {
64        target.center()
65    }
66}
67
68impl Locate<ArcBetweenPoints> for Focus {
69    fn locate(&self, target: &ArcBetweenPoints) -> DVec3 {
70        target.center()
71    }
72}
73
74impl Locate<Circle> for Origin {
75    fn locate(&self, _target: &Circle) -> DVec3 {
76        DVec3::ZERO
77    }
78}
79
80impl Locate<Circle> for Focus {
81    fn locate(&self, _target: &Circle) -> DVec3 {
82        DVec3::ZERO
83    }
84}
85
86fn ellipse_focus(radius: DVec2) -> DVec3 {
87    let DVec2 { x: rx, y: ry } = radius;
88    let c = (rx * rx - ry * ry).abs().sqrt();
89    if rx > ry { DVec3::X * c } else { DVec3::Y * c }
90}
91
92impl Locate<EllipticArc> for Origin {
93    fn locate(&self, _target: &EllipticArc) -> DVec3 {
94        DVec3::ZERO
95    }
96}
97
98impl Locate<EllipticArc> for Focus {
99    fn locate(&self, target: &EllipticArc) -> DVec3 {
100        let focus = ellipse_focus(target.radius);
101        if matches!(self, Focus::Pos) {
102            focus
103        } else {
104            -focus
105        }
106    }
107}
108
109impl Locate<Ellipse> for Origin {
110    fn locate(&self, _target: &Ellipse) -> DVec3 {
111        DVec3::ZERO
112    }
113}
114
115impl Locate<Ellipse> for Focus {
116    fn locate(&self, target: &Ellipse) -> DVec3 {
117        let focus = ellipse_focus(target.radius);
118        if matches!(self, Focus::Pos) {
119            focus
120        } else {
121            -focus
122        }
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129    use ranim_core::{
130        glam::{DAffine3, dvec2, dvec3},
131        prelude::TransformedExt,
132    };
133
134    #[test]
135    fn transformed_focus_is_located_in_local_space_then_moved() {
136        let ellipse = Ellipse::new(dvec2(5.0, 3.0));
137        let placed = ellipse.transformed(DAffine3::from_translation(dvec3(2.0, 0.0, 0.0)));
138        assert_eq!(Focus::Pos.locate(&placed), dvec3(6.0, 0.0, 0.0));
139    }
140}