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