Skip to main content

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