Skip to main content

ranim_items/vitem/geometry/
arc.rs

1use ranim_core::{
2    Extract,
3    anchor::{Aabb, AabbPoint, Locate},
4    core_item::CoreItem,
5    traits::{ApplyTransform, Opacity, ScaleTransform, ShiftTransform, StrokeColor},
6};
7use ranim_core::{
8    color::{AlphaColor, Srgb},
9    glam::{self, DVec3},
10};
11
12use crate::vitem::geometry::EllipticArc;
13use crate::vitem::{DEFAULT_STROKE_WIDTH, VItem};
14
15/// An arc centered at the origin in the XY plane.
16#[derive(Clone, Debug, ranim_macros::Interpolatable)]
17pub struct Arc {
18    /// Radius.
19    pub radius: f64,
20    /// Span angle in radians.
21    pub angle: f64,
22    /// Stroke rgba.
23    pub stroke_rgba: AlphaColor<Srgb>,
24    /// Stroke width.
25    pub stroke_width: f32,
26}
27
28impl Arc {
29    /// Creates an arc with the given angle and radius.
30    pub fn new(angle: f64, radius: f64) -> Self {
31        Self {
32            radius,
33            angle,
34            stroke_rgba: AlphaColor::WHITE,
35            stroke_width: DEFAULT_STROKE_WIDTH,
36        }
37    }
38
39    /// Scales the intrinsic radius.
40    pub fn scale(&mut self, scale: f64) -> &mut Self {
41        self.radius *= scale;
42        self
43    }
44
45    /// The start point in canonical local coordinates.
46    pub fn start(&self) -> DVec3 {
47        DVec3::X * self.radius
48    }
49
50    /// The end point in canonical local coordinates.
51    pub fn end(&self) -> DVec3 {
52        DVec3::new(self.angle.cos(), self.angle.sin(), 0.0) * self.radius
53    }
54}
55
56impl Aabb for Arc {
57    fn aabb(&self) -> [DVec3; 2] {
58        VItem::from(self.clone()).aabb()
59    }
60}
61
62impl Opacity for Arc {
63    fn set_opacity(&mut self, opacity: f32) -> &mut Self {
64        self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
65        self
66    }
67}
68
69impl StrokeColor for Arc {
70    fn stroke_color(&self) -> AlphaColor<Srgb> {
71        self.stroke_rgba
72    }
73    fn set_stroke_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
74        self.stroke_rgba = color;
75        self
76    }
77    fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self {
78        self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
79        self
80    }
81}
82
83impl From<Arc> for VItem {
84    fn from(value: Arc) -> Self {
85        EllipticArc::from(value).into()
86    }
87}
88
89impl Extract for Arc {
90    type Target = CoreItem;
91    fn extract_into(&self, buf: &mut Vec<Self::Target>) {
92        VItem::from(self.clone()).extract_into(buf);
93    }
94}
95
96/// An arc whose local geometry is defined by its start and end points.
97#[derive(Clone, Debug, ranim_macros::Interpolatable)]
98pub struct ArcBetweenPoints {
99    /// Start point.
100    pub start: DVec3,
101    /// End point.
102    pub end: DVec3,
103    /// Arc angle.
104    pub angle: f64,
105    /// Stroke rgba.
106    pub stroke_rgba: AlphaColor<Srgb>,
107    /// Stroke width.
108    pub stroke_width: f32,
109}
110
111impl ArcBetweenPoints {
112    /// Creates an arc between two local points.
113    pub fn new(start: DVec3, end: DVec3, angle: f64) -> Self {
114        Self {
115            start,
116            end,
117            angle,
118            stroke_rgba: AlphaColor::WHITE,
119            stroke_width: DEFAULT_STROKE_WIDTH,
120        }
121    }
122
123    /// Returns the circle center in local coordinates.
124    pub fn center(&self) -> DVec3 {
125        let chord = self.end - self.start;
126        let midpoint = (self.start + self.end) / 2.0;
127        let perpendicular = DVec3::Z.cross(chord).normalize_or_zero();
128        midpoint + perpendicular * (chord.length() / (2.0 * (self.angle / 2.0).tan()))
129    }
130
131    /// Scales the intrinsic start and end points about their AABB center.
132    pub fn scale(&mut self, scale: f64) -> &mut Self {
133        self.scale_at(scale, AabbPoint::CENTER)
134    }
135
136    /// Scales the intrinsic start and end points about an anchor.
137    pub fn scale_at<T>(&mut self, scale: f64, anchor: T) -> &mut Self
138    where
139        T: Locate<Self>,
140    {
141        let point = anchor.locate(self);
142        self.start
143            .shift(-point)
144            .scale(DVec3::splat(scale))
145            .shift(point);
146        self.end
147            .shift(-point)
148            .scale(DVec3::splat(scale))
149            .shift(point);
150        self
151    }
152}
153
154impl Aabb for ArcBetweenPoints {
155    fn aabb(&self) -> [DVec3; 2] {
156        VItem::from(self.clone()).aabb()
157    }
158}
159
160impl<G: Into<ranim_core::prelude::Similarity>> ApplyTransform<G> for ArcBetweenPoints {
161    fn apply(&mut self, transform: G) -> &mut Self {
162        let transform = transform.into();
163        self.start = transform.transform_point(self.start);
164        self.end = transform.transform_point(self.end);
165        self
166    }
167}
168
169impl Opacity for ArcBetweenPoints {
170    fn set_opacity(&mut self, opacity: f32) -> &mut Self {
171        self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
172        self
173    }
174}
175
176impl StrokeColor for ArcBetweenPoints {
177    fn stroke_color(&self) -> AlphaColor<Srgb> {
178        self.stroke_rgba
179    }
180    fn set_stroke_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
181        self.stroke_rgba = color;
182        self
183    }
184    fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self {
185        self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
186        self
187    }
188}
189
190impl From<ArcBetweenPoints> for VItem {
191    fn from(value: ArcBetweenPoints) -> Self {
192        let center = value.center();
193        let radius = value.start.distance(center);
194        let start = value.start - center;
195        let start_angle = start.y.atan2(start.x);
196        let mut item = VItem::from(EllipticArc {
197            radius: glam::DVec2::splat(radius),
198            start_angle,
199            angle: value.angle,
200            stroke_rgba: value.stroke_rgba,
201            stroke_width: value.stroke_width,
202        });
203        item.shift(center);
204        item
205    }
206}
207
208impl Extract for ArcBetweenPoints {
209    type Target = CoreItem;
210    fn extract_into(&self, buf: &mut Vec<Self::Target>) {
211        VItem::from(self.clone()).extract_into(buf);
212    }
213}
214
215#[cfg(test)]
216mod tests {
217    use std::f64::consts::PI;
218
219    use assert_float_eq::assert_float_absolute_eq;
220    use glam::dvec3;
221
222    use super::*;
223
224    #[test]
225    fn arcs_preserve_their_endpoints() {
226        let arc = Arc::new(PI / 2.0, 2.0);
227        assert_float_absolute_eq!(
228            arc.start().distance_squared(dvec3(2.0, 0.0, 0.0)),
229            0.0,
230            1e-10
231        );
232        assert_float_absolute_eq!(arc.end().distance_squared(dvec3(0.0, 2.0, 0.0)), 0.0, 1e-10);
233
234        let arc = ArcBetweenPoints::new(dvec3(2.0, 0.0, 0.0), dvec3(0.0, 2.0, 0.0), PI / 2.0);
235        assert_float_absolute_eq!(arc.center().distance_squared(DVec3::ZERO), 0.0, 1e-10);
236        let item = VItem::from(arc);
237        assert_float_absolute_eq!(
238            item.vpoints[0].distance_squared(dvec3(2.0, 0.0, 0.0)),
239            0.0,
240            1e-10
241        );
242        assert_float_absolute_eq!(
243            item.vpoints[item.vpoints.len() - 1].distance_squared(dvec3(0.0, 2.0, 0.0)),
244            0.0,
245            1e-10
246        );
247    }
248}