Skip to main content

ranim_items/vitem/geometry/
elliptic_arc.rs

1#[cfg(feature = "render")]
2use ranim_render::extract::{Extract, ExtractContext};
3
4use std::f64::consts::TAU;
5
6use ranim_core::{
7    color::{AlphaColor, Srgb},
8    components::vpoint::VPointVec,
9    glam::{DVec2, DVec3},
10    traits::{Aabb, Discard, Opacity, StrokeColor, StrokeWidth, With},
11};
12
13use crate::vitem::{
14    DEFAULT_STROKE_WIDTH, VItem,
15    geometry::{Arc, Circle, Ellipse},
16};
17
18/// An elliptic arc centered at the origin in the XY plane.
19#[derive(Debug, Clone, ranim_macros::Interpolatable)]
20pub struct EllipticArc {
21    /// Semi-axes in the x and y directions.
22    pub radius: DVec2,
23    /// Start angle in radians.
24    pub start_angle: f64,
25    /// Span angle in radians.
26    pub angle: f64,
27    /// Stroke rgba.
28    pub stroke_rgba: AlphaColor<Srgb>,
29    /// Stroke width.
30    pub stroke_width: f32,
31}
32
33impl EllipticArc {
34    /// Creates a new elliptic arc.
35    pub fn new(start_angle: f64, angle: f64, radius: DVec2) -> Self {
36        Self {
37            radius,
38            start_angle,
39            angle,
40            stroke_rgba: AlphaColor::WHITE,
41            stroke_width: DEFAULT_STROKE_WIDTH,
42        }
43    }
44
45    fn generate_vpoints(&self) -> Vec<DVec3> {
46        const NUM_SEGMENTS: usize = 8;
47        let len = 2 * NUM_SEGMENTS + 1;
48        let DVec2 { x: rx, y: ry } = self.radius;
49        let mut vpoints = (0..len)
50            .map(|i| i as f64 / NUM_SEGMENTS as f64 / 2.0 * self.angle + self.start_angle)
51            .map(|theta| {
52                let (mut x, mut y) = (theta.cos(), theta.sin());
53                if x.abs() < 1.8e-7 {
54                    x = 0.0;
55                }
56                if y.abs() < 1.8e-7 {
57                    y = 0.0;
58                }
59                DVec3::new(x * rx, y * ry, 0.0)
60            })
61            .collect::<Vec<_>>();
62
63        let k = (self.angle / NUM_SEGMENTS as f64 / 2.0).cos();
64        vpoints.iter_mut().skip(1).step_by(2).for_each(|p| *p /= k);
65        vpoints
66    }
67}
68
69impl From<Arc> for EllipticArc {
70    fn from(value: Arc) -> Self {
71        let Arc {
72            radius,
73            angle,
74            stroke_rgba,
75            stroke_width,
76        } = value;
77        Self {
78            radius: DVec2::splat(radius),
79            start_angle: 0.0,
80            angle,
81            stroke_rgba,
82            stroke_width,
83        }
84    }
85}
86
87impl From<Circle> for EllipticArc {
88    fn from(value: Circle) -> Self {
89        let Circle {
90            radius,
91            stroke_rgba,
92            stroke_width,
93            ..
94        } = value;
95        Self {
96            radius: DVec2::splat(radius),
97            start_angle: 0.0,
98            angle: TAU,
99            stroke_rgba,
100            stroke_width,
101        }
102    }
103}
104
105impl From<EllipticArc> for VItem {
106    fn from(value: EllipticArc) -> Self {
107        let stroke_rgba = value.stroke_rgba;
108        let stroke_width = value.stroke_width;
109        VItem::from_vpoints(value.generate_vpoints()).with(|vitem| {
110            vitem
111                .set_stroke_color(stroke_rgba)
112                .set_stroke_width(stroke_width)
113                .discard()
114        })
115    }
116}
117
118impl From<Ellipse> for EllipticArc {
119    fn from(value: Ellipse) -> Self {
120        let Ellipse {
121            radius,
122            stroke_rgba,
123            stroke_width,
124            ..
125        } = value;
126        Self {
127            radius,
128            start_angle: 0.0,
129            angle: TAU,
130            stroke_rgba,
131            stroke_width,
132        }
133    }
134}
135
136impl Aabb for EllipticArc {
137    fn aabb(&self) -> [DVec3; 2] {
138        VPointVec(self.generate_vpoints()).aabb()
139    }
140}
141
142#[cfg(feature = "render")]
143impl Extract for EllipticArc {
144    fn extract(&self, ctx: &mut ExtractContext<'_>) {
145        VItem::from(self.clone()).extract(ctx);
146    }
147}
148
149impl StrokeColor for EllipticArc {
150    fn stroke_color(&self) -> AlphaColor<Srgb> {
151        self.stroke_rgba
152    }
153    fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self {
154        self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
155        self
156    }
157    fn set_stroke_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
158        self.stroke_rgba = color;
159        self
160    }
161}
162
163impl Opacity for EllipticArc {
164    fn set_opacity(&mut self, opacity: f32) -> &mut Self {
165        self.set_stroke_opacity(opacity);
166        self
167    }
168}