ranim_items/vitem/geometry/
elliptic_arc.rs1use std::f64::consts::TAU;
2
3use ranim_core::{
4 Extract,
5 color::{AlphaColor, Srgb},
6 components::vpoint::VPointVec,
7 core_item::{CoreItem, vitem::DEFAULT_STROKE_WIDTH},
8 glam::{DVec2, DVec3},
9 traits::{Aabb, Discard, Opacity, StrokeColor, StrokeWidth, With},
10};
11
12use crate::vitem::{
13 VItem,
14 geometry::{Arc, Circle, Ellipse},
15};
16
17#[derive(Debug, Clone, ranim_macros::Interpolatable)]
19pub struct EllipticArc {
20 pub radius: DVec2,
22 pub start_angle: f64,
24 pub angle: f64,
26 pub stroke_rgba: AlphaColor<Srgb>,
28 pub stroke_width: f32,
30}
31
32impl EllipticArc {
33 pub fn new(start_angle: f64, angle: f64, radius: DVec2) -> Self {
35 Self {
36 radius,
37 start_angle,
38 angle,
39 stroke_rgba: AlphaColor::WHITE,
40 stroke_width: DEFAULT_STROKE_WIDTH,
41 }
42 }
43
44 fn generate_vpoints(&self) -> Vec<DVec3> {
45 const NUM_SEGMENTS: usize = 8;
46 let len = 2 * NUM_SEGMENTS + 1;
47 let DVec2 { x: rx, y: ry } = self.radius;
48 let mut vpoints = (0..len)
49 .map(|i| i as f64 / NUM_SEGMENTS as f64 / 2.0 * self.angle + self.start_angle)
50 .map(|theta| {
51 let (mut x, mut y) = (theta.cos(), theta.sin());
52 if x.abs() < 1.8e-7 {
53 x = 0.0;
54 }
55 if y.abs() < 1.8e-7 {
56 y = 0.0;
57 }
58 DVec3::new(x * rx, y * ry, 0.0)
59 })
60 .collect::<Vec<_>>();
61
62 let k = (self.angle / NUM_SEGMENTS as f64 / 2.0).cos();
63 vpoints.iter_mut().skip(1).step_by(2).for_each(|p| *p /= k);
64 vpoints
65 }
66}
67
68impl From<Arc> for EllipticArc {
69 fn from(value: Arc) -> Self {
70 let Arc {
71 radius,
72 angle,
73 stroke_rgba,
74 stroke_width,
75 } = value;
76 Self {
77 radius: DVec2::splat(radius),
78 start_angle: 0.0,
79 angle,
80 stroke_rgba,
81 stroke_width,
82 }
83 }
84}
85
86impl From<Circle> for EllipticArc {
87 fn from(value: Circle) -> Self {
88 let Circle {
89 radius,
90 stroke_rgba,
91 stroke_width,
92 ..
93 } = value;
94 Self {
95 radius: DVec2::splat(radius),
96 start_angle: 0.0,
97 angle: TAU,
98 stroke_rgba,
99 stroke_width,
100 }
101 }
102}
103
104impl From<EllipticArc> for VItem {
105 fn from(value: EllipticArc) -> Self {
106 let stroke_rgba = value.stroke_rgba;
107 let stroke_width = value.stroke_width;
108 VItem::from_vpoints(value.generate_vpoints()).with(|vitem| {
109 vitem
110 .set_stroke_color(stroke_rgba)
111 .set_stroke_width(stroke_width)
112 .discard()
113 })
114 }
115}
116
117impl From<Ellipse> for EllipticArc {
118 fn from(value: Ellipse) -> Self {
119 let Ellipse {
120 radius,
121 stroke_rgba,
122 stroke_width,
123 ..
124 } = value;
125 Self {
126 radius,
127 start_angle: 0.0,
128 angle: TAU,
129 stroke_rgba,
130 stroke_width,
131 }
132 }
133}
134
135impl Aabb for EllipticArc {
136 fn aabb(&self) -> [DVec3; 2] {
137 VPointVec(self.generate_vpoints()).aabb()
138 }
139}
140
141impl Extract for EllipticArc {
142 type Target = CoreItem;
143 fn extract_into(&self, buf: &mut Vec<Self::Target>) {
144 VItem::from(self.clone()).extract_into(buf);
145 }
146}
147
148impl StrokeColor for EllipticArc {
149 fn stroke_color(&self) -> AlphaColor<Srgb> {
150 self.stroke_rgba
151 }
152 fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self {
153 self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
154 self
155 }
156 fn set_stroke_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
157 self.stroke_rgba = color;
158 self
159 }
160}
161
162impl Opacity for EllipticArc {
163 fn set_opacity(&mut self, opacity: f32) -> &mut Self {
164 self.set_stroke_opacity(opacity);
165 self
166 }
167}