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;
6use ranim_core::core_item::vitem::Basis2d;
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    /// Projection
22    pub basis: Basis2d,
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            basis: Basis2d::default(),
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.basis.u()
74    }
75    /// The end point
76    pub fn end(&self) -> DVec3 {
77        let u = self.angle.cos() * self.basis.u();
78        let v = self.angle.sin() * self.basis.v();
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.basis.rotate_on_axis(axis, angle);
102        self
103    }
104}
105
106impl Opacity for Arc {
107    fn set_opacity(&mut self, opacity: f32) -> &mut Self {
108        self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
109        self
110    }
111}
112
113impl StrokeColor for Arc {
114    fn stroke_color(&self) -> AlphaColor<Srgb> {
115        self.stroke_rgba
116    }
117    fn set_stroke_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
118        self.stroke_rgba = color;
119        self
120    }
121    fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self {
122        self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
123        self
124    }
125}
126
127// MARK: Conversions
128impl From<Arc> for VItem {
129    fn from(value: Arc) -> Self {
130        EllipticArc::from(value).into()
131    }
132}
133
134impl Extract for Arc {
135    type Target = CoreItem;
136    fn extract_into(&self, buf: &mut Vec<Self::Target>) {
137        VItem::from(self.clone()).extract_into(buf);
138    }
139}
140
141// MARK: ### ArcBetweenPoints ###
142/// An arc between points
143#[derive(Clone, Debug, ranim_macros::Interpolatable)]
144pub struct ArcBetweenPoints {
145    /// Projection
146    pub basis: Basis2d,
147    /// Start point
148    pub start: DVec3,
149    /// End point
150    pub end: DVec3,
151    /// Arc angle
152    pub angle: f64,
153
154    /// Stroke rgba
155    pub stroke_rgba: AlphaColor<Srgb>,
156    /// Stroke width
157    pub stroke_width: f32,
158}
159
160impl ArcBetweenPoints {
161    /// Constructor
162    pub fn new(start: DVec3, end: DVec3, angle: f64) -> Self {
163        Self {
164            basis: Basis2d::default(),
165            start,
166            end,
167            angle,
168
169            stroke_rgba: AlphaColor::WHITE,
170            stroke_width: DEFAULT_STROKE_WIDTH,
171        }
172    }
173    /// Scale the arc by the given scale, with the given anchor as the center.
174    ///
175    /// Note that this accepts a `f64` scale dispite of [`ranim_core::traits::ScaleTransform`]'s `DVec3`,
176    /// because this keeps the arc a arc.
177    pub fn scale(&mut self, scale: f64) -> &mut Self {
178        self.scale_at(scale, AabbPoint::CENTER)
179    }
180    /// Scale the arc by the given scale, with the given anchor as the center.
181    ///
182    /// Note that this accepts a `f64` scale dispite of [`ranim_core::traits::ScaleTransform`]'s `DVec3`,
183    /// because this keeps the arc a arc.
184    pub fn scale_at<T>(&mut self, scale: f64, anchor: T) -> &mut Self
185    where
186        T: Locate<Self>,
187    {
188        let point = anchor.locate(self);
189        self.start
190            .shift(-point)
191            .scale(DVec3::splat(scale))
192            .shift(point);
193        self.end
194            .shift(-point)
195            .scale(DVec3::splat(scale))
196            .shift(point);
197        self
198    }
199}
200
201// MARK: Traits impl
202impl Aabb for ArcBetweenPoints {
203    /// Note that the arc's bounding box is actually same as the circle's bounding box.
204    fn aabb(&self) -> [DVec3; 2] {
205        // TODO: optimize this
206        Arc::from(self.clone()).aabb()
207    }
208}
209
210impl ShiftTransform for ArcBetweenPoints {
211    fn shift(&mut self, shift: DVec3) -> &mut Self {
212        self.start.shift(shift);
213        self.end.shift(shift);
214        self
215    }
216}
217
218impl RotateTransform for ArcBetweenPoints {
219    fn rotate_on_axis(&mut self, axis: DVec3, angle: f64) -> &mut Self {
220        self.start.rotate_on_axis(axis, angle);
221        self.end.rotate_on_axis(axis, angle);
222        self.basis.rotate_on_axis(axis, angle);
223        self
224    }
225}
226
227impl Opacity for ArcBetweenPoints {
228    fn set_opacity(&mut self, opacity: f32) -> &mut Self {
229        self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
230        self
231    }
232}
233
234impl StrokeColor for ArcBetweenPoints {
235    fn stroke_color(&self) -> AlphaColor<Srgb> {
236        self.stroke_rgba
237    }
238    fn set_stroke_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
239        self.stroke_rgba = color;
240        self
241    }
242    fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self {
243        self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
244        self
245    }
246}
247
248// MARK: Conversions
249impl From<ArcBetweenPoints> for Arc {
250    fn from(value: ArcBetweenPoints) -> Arc {
251        let ArcBetweenPoints {
252            basis: proj,
253            start,
254            end,
255            angle,
256            stroke_rgba,
257            stroke_width,
258        } = value;
259        let radius = (start.distance(end) / 2.0) / (angle / 2.0).sin();
260
261        Arc {
262            basis: proj,
263            angle,
264            radius,
265            center: DVec3::ZERO,
266            stroke_rgba,
267            stroke_width,
268        }
269        .with(|arc| {
270            let cur_start = arc.start();
271
272            let v1 = arc.end() - arc.start();
273            let v2 = end - start;
274
275            let rot_angle = v1.angle_between(v2);
276            let mut rot_axis = v1.cross(v2);
277            if rot_axis.length_squared() <= f64::EPSILON {
278                rot_axis = DVec3::NEG_Z;
279            }
280            rot_axis = rot_axis.normalize();
281            arc.shift(start - cur_start);
282            arc.shift(-start);
283            arc.rotate_on_axis(rot_axis, rot_angle);
284            arc.shift(start);
285        })
286    }
287}
288
289impl From<ArcBetweenPoints> for VItem {
290    fn from(value: ArcBetweenPoints) -> Self {
291        Arc::from(value).into()
292    }
293}
294
295impl Extract for ArcBetweenPoints {
296    type Target = CoreItem;
297    fn extract_into(&self, buf: &mut Vec<Self::Target>) {
298        Arc::from(self.clone()).extract_into(buf);
299    }
300}
301
302#[cfg(test)]
303mod tests {
304    use std::f64::consts::PI;
305
306    use assert_float_eq::assert_float_absolute_eq;
307    use glam::dvec3;
308    use ranim_core::traits::ShiftTransformExt;
309
310    use crate::vitem::geometry::anchor::Origin;
311
312    use super::*;
313
314    #[test]
315    fn test_arc() {
316        let arc = Arc::new(PI / 2.0, 2.0);
317        assert_float_absolute_eq!(
318            arc.start().distance_squared(dvec3(2.0, 0.0, 0.0)),
319            0.0,
320            1e-10
321        );
322        assert_float_absolute_eq!(arc.end().distance_squared(dvec3(0.0, 2.0, 0.0)), 0.0, 1e-10);
323
324        let arc_between_points =
325            ArcBetweenPoints::new(dvec3(2.0, 0.0, 0.0), dvec3(0.0, 2.0, 0.0), PI / 2.0);
326        let arc_between_points = Arc::from(arc_between_points);
327        assert_float_absolute_eq!(
328            arc.center.distance_squared(arc_between_points.center),
329            0.0,
330            1e-10
331        );
332        assert_float_absolute_eq!(arc.radius - arc_between_points.radius, 0.0, 1e-10);
333        assert_float_absolute_eq!(arc.angle - arc_between_points.angle, 0.0, 1e-10);
334
335        let arc_between_points =
336            ArcBetweenPoints::new(dvec3(0.0, 2.0, 0.0), dvec3(2.0, 0.0, 0.0), PI / 2.0);
337        let arc_between_points = Arc::from(arc_between_points);
338        let arc = Arc::new(PI / 2.0, 2.0).with(|arc| {
339            arc.with_origin(Origin, |x| {
340                x.rotate_on_axis(DVec3::NEG_Z, PI);
341            })
342            .shift(dvec3(2.0, 2.0, 0.0));
343        });
344        assert_float_absolute_eq!(
345            arc.center.distance_squared(arc_between_points.center),
346            0.0,
347            1e-10
348        );
349        assert_float_absolute_eq!(arc.radius - arc_between_points.radius, 0.0, 1e-10);
350        assert_float_absolute_eq!(arc.angle - arc_between_points.angle, 0.0, 1e-10);
351    }
352}