ranim_items/vitem/geometry/
elliptic_arc.rs

1use std::f64::consts::TAU;
2
3use ranim_core::{
4    Extract,
5    color::{AlphaColor, Srgb},
6    components::vpoint::VPointVec,
7    core_item::{
8        CoreItem,
9        vitem::{Basis2d, DEFAULT_STROKE_WIDTH},
10    },
11    glam::{DVec2, DVec3},
12    traits::{
13        Aabb, Discard, Opacity, RotateTransform, ShiftTransform, StrokeColor, StrokeWidth, With,
14    },
15};
16
17use crate::vitem::{
18    VItem,
19    geometry::{Arc, Circle, Ellipse},
20};
21
22/// An elliptic arc.
23#[derive(Debug, Clone, ranim_macros::Interpolatable)]
24pub struct EllipticArc {
25    /// Basis
26    pub basis: Basis2d,
27    /// Center
28    pub center: DVec3,
29    /// Semi-axes in the x and y directions
30    pub radius: DVec2,
31    /// Start angle (measured by the theta parameter in parametric equation of the ellipse) in radians
32    pub start_angle: f64,
33    /// Span angle in radians
34    pub angle: f64,
35    /// Stroke rgba
36    pub stroke_rgba: AlphaColor<Srgb>,
37    /// Stroke width
38    pub stroke_width: f32,
39}
40
41impl EllipticArc {
42    /// Creates a new elliptic arc.
43    pub fn new(start_angle: f64, angle: f64, radius: DVec2) -> Self {
44        EllipticArc {
45            basis: Basis2d::default(),
46            center: DVec3::ZERO,
47            radius,
48            start_angle,
49            angle,
50            stroke_rgba: AlphaColor::WHITE,
51            stroke_width: DEFAULT_STROKE_WIDTH,
52        }
53    }
54
55    fn generate_vpoints(&self) -> Vec<DVec3> {
56        const NUM_SEGMENTS: usize = 8;
57        let len = 2 * NUM_SEGMENTS + 1;
58
59        let &EllipticArc {
60            basis,
61            center,
62            radius,
63            start_angle,
64            angle,
65            ..
66        } = self;
67
68        let (u, v) = basis.uv();
69        let DVec2 { x: rx, y: ry } = radius;
70        let mut vpoints = (0..len)
71            .map(|i| i as f64 / NUM_SEGMENTS as f64 / 2. * angle + start_angle)
72            .map(|theta| {
73                let (mut x, mut y) = (theta.cos(), theta.sin());
74                if x.abs() < 1.8e-7 {
75                    x = 0.;
76                }
77                if y.abs() < 1.8e-7 {
78                    y = 0.;
79                }
80                x * rx * u + y * ry * v
81            })
82            .collect::<Vec<_>>();
83
84        let k = (angle / NUM_SEGMENTS as f64 / 2.).cos();
85        vpoints.iter_mut().skip(1).step_by(2).for_each(|p| *p /= k);
86        vpoints.shift(center);
87        vpoints
88    }
89}
90
91impl From<Arc> for EllipticArc {
92    fn from(value: Arc) -> Self {
93        let Arc {
94            basis,
95            center,
96            radius,
97            angle,
98            stroke_rgba,
99            stroke_width,
100        } = value;
101        EllipticArc {
102            basis,
103            center,
104            radius: DVec2::splat(radius),
105            start_angle: 0.,
106            angle,
107            stroke_rgba,
108            stroke_width,
109        }
110    }
111}
112
113impl From<Circle> for EllipticArc {
114    fn from(value: Circle) -> Self {
115        let Circle {
116            basis,
117            center,
118            radius,
119            stroke_rgba,
120            stroke_width,
121            ..
122        } = value;
123        EllipticArc {
124            basis,
125            center,
126            radius: DVec2::splat(radius),
127            start_angle: 0.,
128            angle: TAU,
129            stroke_rgba,
130            stroke_width,
131        }
132    }
133}
134
135impl From<EllipticArc> for VItem {
136    fn from(value: EllipticArc) -> Self {
137        let EllipticArc {
138            stroke_rgba,
139            stroke_width,
140            ..
141        } = value;
142        VItem::from_vpoints(value.generate_vpoints()).with(|vitem| {
143            vitem
144                .set_stroke_color(stroke_rgba)
145                .set_stroke_width(stroke_width)
146                .discard()
147        })
148    }
149}
150
151impl From<Ellipse> for EllipticArc {
152    fn from(value: Ellipse) -> Self {
153        let Ellipse {
154            basis,
155            center,
156            radius,
157            stroke_rgba,
158            stroke_width,
159            ..
160        } = value;
161        EllipticArc {
162            basis,
163            center,
164            radius,
165            start_angle: 0.,
166            angle: TAU,
167            stroke_rgba,
168            stroke_width,
169        }
170    }
171}
172
173impl Aabb for EllipticArc {
174    fn aabb(&self) -> [DVec3; 2] {
175        // TODO: maybe calculate AABB by linear algebra?
176        // that would be extremely complicated
177        VPointVec(self.generate_vpoints()).aabb()
178    }
179}
180
181impl Extract for EllipticArc {
182    type Target = CoreItem;
183    fn extract_into(&self, buf: &mut Vec<Self::Target>) {
184        VItem::from(self.clone()).extract_into(buf);
185    }
186}
187
188impl StrokeColor for EllipticArc {
189    fn stroke_color(&self) -> AlphaColor<Srgb> {
190        self.stroke_rgba
191    }
192    fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self {
193        self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
194        self
195    }
196    fn set_stroke_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
197        self.stroke_rgba = color;
198        self
199    }
200}
201
202impl Opacity for EllipticArc {
203    fn set_opacity(&mut self, opacity: f32) -> &mut Self {
204        self.set_stroke_opacity(opacity);
205        self
206    }
207}
208
209impl ShiftTransform for EllipticArc {
210    fn shift(&mut self, shift: DVec3) -> &mut Self {
211        self.center += shift;
212        self
213    }
214}
215
216impl RotateTransform for EllipticArc {
217    fn rotate_on_axis(&mut self, axis: DVec3, angle: f64) -> &mut Self {
218        self.basis.rotate_on_axis(axis, angle);
219        self.center.rotate_on_axis(axis, angle);
220        self
221    }
222}