ranim_items/vitem/geometry/
arc.rs1#[cfg(feature = "render")]
2use ranim_render::extract::{Extract, ExtractContext};
3
4use ranim_core::{
5 anchor::{Aabb, AabbPoint, Locate},
6 traits::{ApplyTransform, Opacity, ScaleTransform, ShiftTransform, StrokeColor},
7};
8use ranim_core::{
9 color::{AlphaColor, Srgb},
10 glam::{self, DVec3},
11};
12
13use crate::vitem::geometry::EllipticArc;
14use crate::vitem::{DEFAULT_STROKE_WIDTH, VItem};
15
16#[derive(Clone, Debug, ranim_macros::Interpolatable)]
18pub struct Arc {
19 pub radius: f64,
21 pub angle: f64,
23 pub stroke_rgba: AlphaColor<Srgb>,
25 pub stroke_width: f32,
27}
28
29impl Arc {
30 pub fn new(angle: f64, radius: f64) -> Self {
32 Self {
33 radius,
34 angle,
35 stroke_rgba: AlphaColor::WHITE,
36 stroke_width: DEFAULT_STROKE_WIDTH,
37 }
38 }
39
40 pub fn scale(&mut self, scale: f64) -> &mut Self {
42 self.radius *= scale;
43 self
44 }
45
46 pub fn start(&self) -> DVec3 {
48 DVec3::X * self.radius
49 }
50
51 pub fn end(&self) -> DVec3 {
53 DVec3::new(self.angle.cos(), self.angle.sin(), 0.0) * self.radius
54 }
55}
56
57impl Aabb for Arc {
58 fn aabb(&self) -> [DVec3; 2] {
59 VItem::from(self.clone()).aabb()
60 }
61}
62
63impl Opacity for Arc {
64 fn set_opacity(&mut self, opacity: f32) -> &mut Self {
65 self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
66 self
67 }
68}
69
70impl StrokeColor for Arc {
71 fn stroke_color(&self) -> AlphaColor<Srgb> {
72 self.stroke_rgba
73 }
74 fn set_stroke_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
75 self.stroke_rgba = color;
76 self
77 }
78 fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self {
79 self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
80 self
81 }
82}
83
84impl From<Arc> for VItem {
85 fn from(value: Arc) -> Self {
86 EllipticArc::from(value).into()
87 }
88}
89
90#[cfg(feature = "render")]
91impl Extract for Arc {
92 fn extract(&self, ctx: &mut ExtractContext<'_>) {
93 VItem::from(self.clone()).extract(ctx);
94 }
95}
96
97#[derive(Clone, Debug, ranim_macros::Interpolatable)]
99pub struct ArcBetweenPoints {
100 pub start: DVec3,
102 pub end: DVec3,
104 pub angle: f64,
106 pub stroke_rgba: AlphaColor<Srgb>,
108 pub stroke_width: f32,
110}
111
112impl ArcBetweenPoints {
113 pub fn new(start: DVec3, end: DVec3, angle: f64) -> Self {
115 Self {
116 start,
117 end,
118 angle,
119 stroke_rgba: AlphaColor::WHITE,
120 stroke_width: DEFAULT_STROKE_WIDTH,
121 }
122 }
123
124 pub fn center(&self) -> DVec3 {
126 let chord = self.end - self.start;
127 let midpoint = (self.start + self.end) / 2.0;
128 let perpendicular = DVec3::Z.cross(chord).normalize_or_zero();
129 midpoint + perpendicular * (chord.length() / (2.0 * (self.angle / 2.0).tan()))
130 }
131
132 pub fn scale(&mut self, scale: f64) -> &mut Self {
134 self.scale_at(scale, AabbPoint::CENTER)
135 }
136
137 pub fn scale_at<T>(&mut self, scale: f64, anchor: T) -> &mut Self
139 where
140 T: Locate<Self>,
141 {
142 let point = anchor.locate(self);
143 self.start
144 .shift(-point)
145 .scale(DVec3::splat(scale))
146 .shift(point);
147 self.end
148 .shift(-point)
149 .scale(DVec3::splat(scale))
150 .shift(point);
151 self
152 }
153}
154
155impl Aabb for ArcBetweenPoints {
156 fn aabb(&self) -> [DVec3; 2] {
157 VItem::from(self.clone()).aabb()
158 }
159}
160
161impl<G: Into<ranim_core::prelude::Similarity>> ApplyTransform<G> for ArcBetweenPoints {
162 fn apply(&mut self, transform: G) -> &mut Self {
163 let transform = transform.into();
164 self.start = transform.transform_point(self.start);
165 self.end = transform.transform_point(self.end);
166 self
167 }
168}
169
170impl Opacity for ArcBetweenPoints {
171 fn set_opacity(&mut self, opacity: f32) -> &mut Self {
172 self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
173 self
174 }
175}
176
177impl StrokeColor for ArcBetweenPoints {
178 fn stroke_color(&self) -> AlphaColor<Srgb> {
179 self.stroke_rgba
180 }
181 fn set_stroke_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
182 self.stroke_rgba = color;
183 self
184 }
185 fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self {
186 self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
187 self
188 }
189}
190
191impl From<ArcBetweenPoints> for VItem {
192 fn from(value: ArcBetweenPoints) -> Self {
193 let center = value.center();
194 let radius = value.start.distance(center);
195 let start = value.start - center;
196 let start_angle = start.y.atan2(start.x);
197 let mut item = VItem::from(EllipticArc {
198 radius: glam::DVec2::splat(radius),
199 start_angle,
200 angle: value.angle,
201 stroke_rgba: value.stroke_rgba,
202 stroke_width: value.stroke_width,
203 });
204 item.shift(center);
205 item
206 }
207}
208
209#[cfg(feature = "render")]
210impl Extract for ArcBetweenPoints {
211 fn extract(&self, ctx: &mut ExtractContext<'_>) {
212 VItem::from(self.clone()).extract(ctx);
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use std::f64::consts::PI;
219
220 use assert_float_eq::assert_float_absolute_eq;
221 use glam::dvec3;
222 use ranim_core::traits::{RotateTransform, ShiftTransform};
223
224 use super::*;
225
226 #[test]
227 fn arcs_preserve_their_endpoints() {
228 let arc = Arc::new(PI / 2.0, 2.0);
229 assert_float_absolute_eq!(
230 arc.start().distance_squared(dvec3(2.0, 0.0, 0.0)),
231 0.0,
232 1e-10
233 );
234 assert_float_absolute_eq!(arc.end().distance_squared(dvec3(0.0, 2.0, 0.0)), 0.0, 1e-10);
235
236 let angle = PI * 2.0 / 3.0;
237 let mut placed = VItem::from(Arc::new(angle, 2.0));
238 placed
239 .rotate_on_axis(DVec3::Z, PI / 2.0 - angle / 2.0)
240 .shift(dvec3(2.0, 2.0, 0.0));
241 assert!(placed.vpoints[0].abs_diff_eq(dvec3(3.732050807568877, 3.0, 0.0), 1e-10));
242 assert!(
243 placed
244 .vpoints
245 .last()
246 .unwrap()
247 .abs_diff_eq(dvec3(0.2679491924311228, 3.0, 0.0), 1e-10)
248 );
249
250 let arc = ArcBetweenPoints::new(dvec3(2.0, 0.0, 0.0), dvec3(0.0, 2.0, 0.0), PI / 2.0);
251 assert_float_absolute_eq!(arc.center().distance_squared(DVec3::ZERO), 0.0, 1e-10);
252 let item = VItem::from(arc);
253 assert_float_absolute_eq!(
254 item.vpoints[0].distance_squared(dvec3(2.0, 0.0, 0.0)),
255 0.0,
256 1e-10
257 );
258 assert_float_absolute_eq!(
259 item.vpoints[item.vpoints.len() - 1].distance_squared(dvec3(0.0, 2.0, 0.0)),
260 0.0,
261 1e-10
262 );
263 }
264}