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