ranim_items/vitem/geometry/
arc.rs

1use color::{AlphaColor, Srgb};
2use glam::DVec3;
3use ranim_core::Extract;
4use ranim_core::anchor::{Aabb, Locate};
5use ranim_core::core_item::CoreItem;
6
7use ranim_core::{color, glam};
8
9use ranim_core::traits::{
10    Opacity, RotateTransform, ScaleTransform, ShiftTransform, StrokeColor, With,
11};
12
13use crate::vitem::geometry::EllipticArc;
14use crate::vitem::{DEFAULT_STROKE_WIDTH, VItem};
15use ranim_core::anchor::AabbPoint;
16
17// MARK: ### Arc ###
18/// An arc
19#[derive(Clone, Debug, ranim_macros::Interpolatable)]
20pub struct Arc {
21    /// Axes
22    pub axes: (DVec3, DVec3),
23    /// Center
24    pub center: DVec3,
25    /// Radius
26    pub radius: f64,
27    /// Angle
28    pub angle: f64,
29
30    /// Stroke rgba
31    pub stroke_rgba: AlphaColor<Srgb>,
32    /// Stroke width
33    pub stroke_width: f32,
34}
35
36impl Arc {
37    /// Constructor
38    pub fn new(angle: f64, radius: f64) -> Self {
39        Self {
40            axes: (DVec3::X, DVec3::Y),
41            center: DVec3::ZERO,
42            radius,
43            angle,
44            stroke_rgba: AlphaColor::WHITE,
45            stroke_width: DEFAULT_STROKE_WIDTH,
46        }
47    }
48    /// Scale the arc by the given scale, with the given anchor as the center.
49    ///
50    /// Note that this accepts a `f64` scale dispite of [`ranim_core::traits::ScaleTransform`]'s `DVec3`,
51    /// because this keeps the arc a arc.
52    pub fn scale(&mut self, scale: f64) -> &mut Self {
53        self.scale_by_anchor(scale, AabbPoint::CENTER)
54    }
55    /// Scale the arc by the given scale, with the given anchor as the center.
56    ///
57    /// Note that this accepts a `f64` scale dispite of [`ranim_core::traits::ScaleTransform`]'s `DVec3`,
58    /// because this keeps the arc a arc.
59    pub fn scale_by_anchor<T>(&mut self, scale: f64, anchor: T) -> &mut Self
60    where
61        T: Locate<Self>,
62    {
63        let anchor = anchor.locate(self);
64        self.radius *= scale;
65        self.center
66            .shift(-anchor)
67            .scale(DVec3::splat(scale))
68            .shift(anchor);
69        self
70    }
71    /// The start point
72    pub fn start(&self) -> DVec3 {
73        self.center + self.radius * self.axes.0.normalize()
74    }
75    /// The end point
76    pub fn end(&self) -> DVec3 {
77        let u = self.angle.cos() * self.axes.0.normalize();
78        let v = self.angle.sin() * self.axes.1.normalize();
79        self.center + self.radius * (u + v)
80    }
81}
82
83// MARK: Traits impl
84impl Aabb for Arc {
85    /// Note that the arc's bounding box is actually same as the circle's bounding box.
86    fn aabb(&self) -> [DVec3; 2] {
87        VItem::from(self.clone()).aabb()
88    }
89}
90
91impl ShiftTransform for Arc {
92    fn shift(&mut self, shift: DVec3) -> &mut Self {
93        self.center.shift(shift);
94        self
95    }
96}
97
98impl RotateTransform for Arc {
99    fn rotate_on_axis(&mut self, axis: DVec3, angle: f64) -> &mut Self {
100        self.center.rotate_on_axis(axis, angle);
101        self.axes.0.rotate_on_axis(axis, angle);
102        self.axes.0 = self.axes.0.normalize();
103        self.axes.1.rotate_on_axis(axis, angle);
104        self.axes.1 = self.axes.1.normalize();
105        self
106    }
107}
108
109impl Opacity for Arc {
110    fn set_opacity(&mut self, opacity: f32) -> &mut Self {
111        self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
112        self
113    }
114}
115
116impl StrokeColor for Arc {
117    fn stroke_color(&self) -> AlphaColor<Srgb> {
118        self.stroke_rgba
119    }
120    fn set_stroke_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
121        self.stroke_rgba = color;
122        self
123    }
124    fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self {
125        self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
126        self
127    }
128}
129
130// MARK: Conversions
131impl From<Arc> for VItem {
132    fn from(value: Arc) -> Self {
133        EllipticArc::from(value).into()
134    }
135}
136
137impl Extract for Arc {
138    type Target = CoreItem;
139    fn extract_into(&self, buf: &mut Vec<Self::Target>) {
140        VItem::from(self.clone()).extract_into(buf);
141    }
142}
143
144// MARK: ### ArcBetweenPoints ###
145/// An arc between points
146#[derive(Clone, Debug, ranim_macros::Interpolatable)]
147pub struct ArcBetweenPoints {
148    /// Axes
149    pub axes: (DVec3, DVec3),
150    /// Start point
151    pub start: DVec3,
152    /// End point
153    pub end: DVec3,
154    /// Arc angle
155    pub angle: f64,
156
157    /// Stroke rgba
158    pub stroke_rgba: AlphaColor<Srgb>,
159    /// Stroke width
160    pub stroke_width: f32,
161}
162
163impl ArcBetweenPoints {
164    /// Constructor
165    pub fn new(start: DVec3, end: DVec3, angle: f64) -> Self {
166        Self {
167            axes: (DVec3::X, DVec3::Y),
168            start,
169            end,
170            angle,
171
172            stroke_rgba: AlphaColor::WHITE,
173            stroke_width: DEFAULT_STROKE_WIDTH,
174        }
175    }
176    /// Scale the arc by the given scale, with the given anchor as the center.
177    ///
178    /// Note that this accepts a `f64` scale dispite of [`ranim_core::traits::ScaleTransform`]'s `DVec3`,
179    /// because this keeps the arc a arc.
180    pub fn scale(&mut self, scale: f64) -> &mut Self {
181        self.scale_at(scale, AabbPoint::CENTER)
182    }
183    /// Scale the arc by the given scale, with the given anchor as the center.
184    ///
185    /// Note that this accepts a `f64` scale dispite of [`ranim_core::traits::ScaleTransform`]'s `DVec3`,
186    /// because this keeps the arc a arc.
187    pub fn scale_at<T>(&mut self, scale: f64, anchor: T) -> &mut Self
188    where
189        T: Locate<Self>,
190    {
191        let point = anchor.locate(self);
192        self.start
193            .shift(-point)
194            .scale(DVec3::splat(scale))
195            .shift(point);
196        self.end
197            .shift(-point)
198            .scale(DVec3::splat(scale))
199            .shift(point);
200        self
201    }
202}
203
204// MARK: Traits impl
205impl Aabb for ArcBetweenPoints {
206    /// Note that the arc's bounding box is actually same as the circle's bounding box.
207    fn aabb(&self) -> [DVec3; 2] {
208        // TODO: optimize this
209        Arc::from(self.clone()).aabb()
210    }
211}
212
213impl ShiftTransform for ArcBetweenPoints {
214    fn shift(&mut self, shift: DVec3) -> &mut Self {
215        self.start.shift(shift);
216        self.end.shift(shift);
217        self
218    }
219}
220
221impl RotateTransform for ArcBetweenPoints {
222    fn rotate_on_axis(&mut self, axis: DVec3, angle: f64) -> &mut Self {
223        self.start.rotate_on_axis(axis, angle);
224        self.end.rotate_on_axis(axis, angle);
225        self.axes.0.rotate_on_axis(axis, angle);
226        self.axes.0 = self.axes.0.normalize();
227        self.axes.1.rotate_on_axis(axis, angle);
228        self.axes.1 = self.axes.1.normalize();
229        self
230    }
231}
232
233impl Opacity for ArcBetweenPoints {
234    fn set_opacity(&mut self, opacity: f32) -> &mut Self {
235        self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
236        self
237    }
238}
239
240impl StrokeColor for ArcBetweenPoints {
241    fn stroke_color(&self) -> AlphaColor<Srgb> {
242        self.stroke_rgba
243    }
244    fn set_stroke_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
245        self.stroke_rgba = color;
246        self
247    }
248    fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self {
249        self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
250        self
251    }
252}
253
254// MARK: Conversions
255impl From<ArcBetweenPoints> for Arc {
256    fn from(value: ArcBetweenPoints) -> Arc {
257        let ArcBetweenPoints {
258            axes: proj,
259            start,
260            end,
261            angle,
262            stroke_rgba,
263            stroke_width,
264        } = value;
265        let radius = (start.distance(end) / 2.0) / (angle / 2.0).sin();
266
267        Arc {
268            axes: proj,
269            angle,
270            radius,
271            center: DVec3::ZERO,
272            stroke_rgba,
273            stroke_width,
274        }
275        .with(|arc| {
276            let cur_start = arc.start();
277
278            let v1 = arc.end() - arc.start();
279            let v2 = end - start;
280
281            let rot_angle = v1.angle_between(v2);
282            let mut rot_axis = v1.cross(v2);
283            if rot_axis.length_squared() <= f64::EPSILON {
284                rot_axis = DVec3::NEG_Z;
285            }
286            rot_axis = rot_axis.normalize();
287            arc.shift(start - cur_start);
288            arc.shift(-start);
289            arc.rotate_on_axis(rot_axis, rot_angle);
290            arc.shift(start);
291        })
292    }
293}
294
295impl From<ArcBetweenPoints> for VItem {
296    fn from(value: ArcBetweenPoints) -> Self {
297        Arc::from(value).into()
298    }
299}
300
301impl Extract for ArcBetweenPoints {
302    type Target = CoreItem;
303    fn extract_into(&self, buf: &mut Vec<Self::Target>) {
304        Arc::from(self.clone()).extract_into(buf);
305    }
306}
307
308#[cfg(test)]
309mod tests {
310    use std::f64::consts::PI;
311
312    use assert_float_eq::assert_float_absolute_eq;
313    use glam::dvec3;
314    use ranim_core::traits::ShiftTransformExt;
315
316    use crate::vitem::geometry::anchor::Origin;
317
318    use super::*;
319
320    #[test]
321    fn test_arc() {
322        let arc = Arc::new(PI / 2.0, 2.0);
323        assert_float_absolute_eq!(
324            arc.start().distance_squared(dvec3(2.0, 0.0, 0.0)),
325            0.0,
326            1e-10
327        );
328        assert_float_absolute_eq!(arc.end().distance_squared(dvec3(0.0, 2.0, 0.0)), 0.0, 1e-10);
329
330        let arc_between_points =
331            ArcBetweenPoints::new(dvec3(2.0, 0.0, 0.0), dvec3(0.0, 2.0, 0.0), PI / 2.0);
332        let arc_between_points = Arc::from(arc_between_points);
333        assert_float_absolute_eq!(
334            arc.center.distance_squared(arc_between_points.center),
335            0.0,
336            1e-10
337        );
338        assert_float_absolute_eq!(arc.radius - arc_between_points.radius, 0.0, 1e-10);
339        assert_float_absolute_eq!(arc.angle - arc_between_points.angle, 0.0, 1e-10);
340
341        let arc_between_points =
342            ArcBetweenPoints::new(dvec3(0.0, 2.0, 0.0), dvec3(2.0, 0.0, 0.0), PI / 2.0);
343        let arc_between_points = Arc::from(arc_between_points);
344        let arc = Arc::new(PI / 2.0, 2.0).with(|arc| {
345            arc.with_origin(Origin, |x| {
346                x.rotate_on_axis(DVec3::NEG_Z, PI);
347            })
348            .shift(dvec3(2.0, 2.0, 0.0));
349        });
350        assert_float_absolute_eq!(
351            arc.center.distance_squared(arc_between_points.center),
352            0.0,
353            1e-10
354        );
355        assert_float_absolute_eq!(arc.radius - arc_between_points.radius, 0.0, 1e-10);
356        assert_float_absolute_eq!(arc.angle - arc_between_points.angle, 0.0, 1e-10);
357    }
358}