ranim_items/vitem/geometry/
arc.rs1use 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#[derive(Clone, Debug, ranim_macros::Interpolatable)]
17pub struct Arc {
18 pub radius: f64,
20 pub angle: f64,
22 pub stroke_rgba: AlphaColor<Srgb>,
24 pub stroke_width: f32,
26}
27
28impl Arc {
29 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 pub fn scale(&mut self, scale: f64) -> &mut Self {
41 self.radius *= scale;
42 self
43 }
44
45 pub fn start(&self) -> DVec3 {
47 DVec3::X * self.radius
48 }
49
50 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#[derive(Clone, Debug, ranim_macros::Interpolatable)]
98pub struct ArcBetweenPoints {
99 pub start: DVec3,
101 pub end: DVec3,
103 pub angle: f64,
105 pub stroke_rgba: AlphaColor<Srgb>,
107 pub stroke_width: f32,
109}
110
111impl ArcBetweenPoints {
112 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 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 pub fn scale(&mut self, scale: f64) -> &mut Self {
133 self.scale_at(scale, AabbPoint::CENTER)
134 }
135
136 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}