Skip to main content

ranim_items/vitem/geometry/
polygon.rs

1#[cfg(feature = "render")]
2use ranim_render::extract::{Extract, ExtractContext};
3
4use std::f64::consts::{PI, TAU};
5
6use color::{AlphaColor, Srgb};
7use itertools::Itertools;
8use ranim_core::{
9    anchor::Aabb,
10    color,
11    glam::{DVec2, DVec3},
12    traits::{Alignable, ApplyTransform, FillColor, Opacity, StrokeColor, StrokeWidth, With},
13};
14
15use crate::vitem::{DEFAULT_STROKE_WIDTH, VItem, geometry::Circle};
16
17/// A square centered at the origin in the XY plane.
18#[derive(Clone, Debug, ranim_macros::Interpolatable)]
19#[allow(missing_docs)]
20pub struct Square {
21    pub size: f64,
22    pub stroke_rgba: AlphaColor<Srgb>,
23    pub stroke_width: f32,
24    pub fill_rgba: AlphaColor<Srgb>,
25}
26
27#[allow(missing_docs)]
28impl Square {
29    pub fn new(size: f64) -> Self {
30        Self {
31            size,
32            stroke_rgba: AlphaColor::WHITE,
33            stroke_width: DEFAULT_STROKE_WIDTH,
34            fill_rgba: AlphaColor::TRANSPARENT,
35        }
36    }
37
38    /// Scales the intrinsic square size.
39    pub fn scale(&mut self, scale: f64) -> &mut Self {
40        self.size *= scale;
41        self
42    }
43}
44
45impl Aabb for Square {
46    fn aabb(&self) -> [DVec3; 2] {
47        let h = self.size / 2.0;
48        [DVec3::new(-h, -h, 0.0), DVec3::new(h, h, 0.0)].aabb()
49    }
50}
51
52impl Alignable for Square {
53    fn is_aligned(&self, _other: &Self) -> bool {
54        true
55    }
56    fn align_with(&mut self, _other: &mut Self) {}
57}
58
59impl Opacity for Square {
60    fn set_opacity(&mut self, opacity: f32) -> &mut Self {
61        self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
62        self.fill_rgba = self.fill_rgba.with_alpha(opacity);
63        self
64    }
65}
66
67impl StrokeColor for Square {
68    fn stroke_color(&self) -> AlphaColor<Srgb> {
69        self.stroke_rgba
70    }
71    fn set_stroke_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
72        self.stroke_rgba = color;
73        self
74    }
75    fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self {
76        self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
77        self
78    }
79}
80
81impl FillColor for Square {
82    fn fill_color(&self) -> AlphaColor<Srgb> {
83        self.fill_rgba
84    }
85    fn set_fill_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
86        self.fill_rgba = color;
87        self
88    }
89    fn set_fill_opacity(&mut self, opacity: f32) -> &mut Self {
90        self.fill_rgba = self.fill_rgba.with_alpha(opacity);
91        self
92    }
93}
94
95impl From<Square> for Rectangle {
96    fn from(value: Square) -> Self {
97        let Square {
98            size,
99            stroke_rgba,
100            stroke_width,
101            fill_rgba,
102        } = value;
103        Rectangle {
104            size: DVec2::splat(size),
105            stroke_rgba,
106            stroke_width,
107            fill_rgba,
108        }
109    }
110}
111
112impl From<Square> for RegularPolygon {
113    fn from(value: Square) -> Self {
114        RegularPolygon::new(4, value.size / 2.0 * 2.0f64.sqrt()).with(|x| {
115            x.stroke_rgba = value.stroke_rgba;
116            x.stroke_width = value.stroke_width;
117            x.fill_rgba = value.fill_rgba;
118        })
119    }
120}
121
122impl From<Square> for Polygon {
123    fn from(value: Square) -> Self {
124        Rectangle::from(value).into()
125    }
126}
127impl From<Square> for VItem {
128    fn from(value: Square) -> Self {
129        Rectangle::from(value).into()
130    }
131}
132#[cfg(feature = "render")]
133impl Extract for Square {
134    fn extract(&self, ctx: &mut ExtractContext<'_>) {
135        VItem::from(self.clone()).extract(ctx);
136    }
137}
138
139/// A rectangle centered at the origin in the XY plane.
140#[derive(Clone, Debug, ranim_macros::Interpolatable)]
141#[allow(missing_docs)]
142pub struct Rectangle {
143    pub size: DVec2,
144    pub stroke_rgba: AlphaColor<Srgb>,
145    pub stroke_width: f32,
146    pub fill_rgba: AlphaColor<Srgb>,
147}
148
149#[allow(missing_docs)]
150impl Rectangle {
151    pub fn new(width: f64, height: f64) -> Self {
152        Self {
153            size: DVec2::new(width, height),
154            stroke_rgba: AlphaColor::WHITE,
155            stroke_width: DEFAULT_STROKE_WIDTH,
156            fill_rgba: AlphaColor::TRANSPARENT,
157        }
158    }
159    pub fn width(&self) -> f64 {
160        self.size.x.abs()
161    }
162    pub fn height(&self) -> f64 {
163        self.size.y.abs()
164    }
165    /// Edits the intrinsic dimensions along the canonical X/Y axes.
166    pub fn scale_axes(&mut self, scale: DVec2) -> &mut Self {
167        self.size *= scale;
168        self
169    }
170}
171
172impl Aabb for Rectangle {
173    fn aabb(&self) -> [DVec3; 2] {
174        let h = self.size / 2.0;
175        [h.extend(0.0), (-h).extend(0.0)].aabb()
176    }
177}
178impl Alignable for Rectangle {
179    fn align_with(&mut self, _other: &mut Self) {}
180    fn is_aligned(&self, _other: &Self) -> bool {
181        true
182    }
183}
184impl Opacity for Rectangle {
185    fn set_opacity(&mut self, opacity: f32) -> &mut Self {
186        self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
187        self.fill_rgba = self.fill_rgba.with_alpha(opacity);
188        self
189    }
190}
191impl StrokeColor for Rectangle {
192    fn stroke_color(&self) -> AlphaColor<Srgb> {
193        self.stroke_rgba
194    }
195    fn set_stroke_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
196        self.stroke_rgba = color;
197        self
198    }
199    fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self {
200        self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
201        self
202    }
203}
204impl FillColor for Rectangle {
205    fn fill_color(&self) -> AlphaColor<Srgb> {
206        self.fill_rgba
207    }
208    fn set_fill_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
209        self.fill_rgba = color;
210        self
211    }
212    fn set_fill_opacity(&mut self, opacity: f32) -> &mut Self {
213        self.fill_rgba = self.fill_rgba.with_alpha(opacity);
214        self
215    }
216}
217
218impl From<Rectangle> for Polygon {
219    fn from(value: Rectangle) -> Self {
220        let h = value.size / 2.0;
221        Self {
222            points: vec![
223                DVec3::new(-h.x, -h.y, 0.0),
224                DVec3::new(h.x, -h.y, 0.0),
225                DVec3::new(h.x, h.y, 0.0),
226                DVec3::new(-h.x, h.y, 0.0),
227            ],
228            stroke_rgba: value.stroke_rgba,
229            stroke_width: value.stroke_width,
230            fill_rgba: value.fill_rgba,
231        }
232    }
233}
234impl From<Rectangle> for VItem {
235    fn from(value: Rectangle) -> Self {
236        Polygon::from(value).into()
237    }
238}
239#[cfg(feature = "render")]
240impl Extract for Rectangle {
241    fn extract(&self, ctx: &mut ExtractContext<'_>) {
242        VItem::from(self.clone()).extract(ctx);
243    }
244}
245
246/// A polygon with uniform stroke and fill.
247#[derive(Clone, Debug, ranim_macros::Interpolatable)]
248#[allow(missing_docs)]
249pub struct Polygon {
250    pub points: Vec<DVec3>,
251    pub stroke_rgba: AlphaColor<Srgb>,
252    pub stroke_width: f32,
253    pub fill_rgba: AlphaColor<Srgb>,
254}
255#[allow(missing_docs)]
256impl Polygon {
257    pub fn new(points: Vec<DVec3>) -> Self {
258        Self {
259            points,
260            stroke_rgba: AlphaColor::WHITE,
261            stroke_width: DEFAULT_STROKE_WIDTH,
262            fill_rgba: AlphaColor::TRANSPARENT,
263        }
264    }
265}
266impl Aabb for Polygon {
267    fn aabb(&self) -> [DVec3; 2] {
268        self.points.aabb()
269    }
270}
271impl<G: Into<ranim_core::glam::DAffine3>> ApplyTransform<G> for Polygon {
272    fn apply(&mut self, transform: G) -> &mut Self {
273        self.points.apply(transform.into());
274        self
275    }
276}
277impl Alignable for Polygon {
278    fn is_aligned(&self, other: &Self) -> bool {
279        self.points.len() == other.points.len()
280    }
281    fn align_with(&mut self, other: &mut Self) {
282        if self.points.len() > other.points.len() {
283            return other.align_with(self);
284        }
285        self.points
286            .resize(other.points.len(), *self.points.last().unwrap());
287    }
288}
289impl Opacity for Polygon {
290    fn set_opacity(&mut self, opacity: f32) -> &mut Self {
291        self.fill_rgba = self.fill_rgba.with_alpha(opacity);
292        self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
293        self
294    }
295}
296impl StrokeColor for Polygon {
297    fn stroke_color(&self) -> AlphaColor<Srgb> {
298        self.stroke_rgba
299    }
300    fn set_stroke_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
301        self.stroke_rgba = color;
302        self
303    }
304    fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self {
305        self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
306        self
307    }
308}
309impl FillColor for Polygon {
310    fn fill_color(&self) -> AlphaColor<Srgb> {
311        self.fill_rgba
312    }
313    fn set_fill_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
314        self.fill_rgba = color;
315        self
316    }
317    fn set_fill_opacity(&mut self, opacity: f32) -> &mut Self {
318        self.fill_rgba = self.fill_rgba.with_alpha(opacity);
319        self
320    }
321}
322impl From<Polygon> for VItem {
323    fn from(value: Polygon) -> Self {
324        let Polygon {
325            mut points,
326            stroke_rgba,
327            stroke_width,
328            fill_rgba,
329        } = value;
330        assert!(points.len() > 2);
331        points.push(points[0]);
332        let handles = points
333            .iter()
334            .tuple_windows()
335            .map(|(&a, &b)| 0.5 * (a + b))
336            .collect::<Vec<_>>();
337        let vpoints = points.into_iter().interleave(handles).collect();
338        VItem::from_vpoints(vpoints).with(|v| {
339            v.set_fill_color(fill_rgba)
340                .set_stroke_color(stroke_rgba)
341                .set_stroke_width(stroke_width);
342        })
343    }
344}
345#[cfg(feature = "render")]
346impl Extract for Polygon {
347    fn extract(&self, ctx: &mut ExtractContext<'_>) {
348        VItem::from(self.clone()).extract(ctx);
349    }
350}
351
352/// A regular polygon centered at the origin in the XY plane.
353#[derive(Debug, Clone, ranim_macros::Interpolatable)]
354#[allow(missing_docs)]
355pub struct RegularPolygon {
356    pub sides: usize,
357    pub radius: f64,
358    pub stroke_rgba: AlphaColor<Srgb>,
359    pub stroke_width: f32,
360    pub fill_rgba: AlphaColor<Srgb>,
361}
362impl Alignable for RegularPolygon {
363    fn is_aligned(&self, _other: &Self) -> bool {
364        true
365    }
366    fn align_with(&mut self, _other: &mut Self) {}
367}
368#[allow(missing_docs)]
369impl RegularPolygon {
370    pub fn new(sides: usize, radius: f64) -> Self {
371        assert!(sides >= 3);
372        Self {
373            sides,
374            radius,
375            stroke_rgba: AlphaColor::WHITE,
376            stroke_width: DEFAULT_STROKE_WIDTH,
377            fill_rgba: AlphaColor::TRANSPARENT,
378        }
379    }
380    pub fn points(&self) -> Vec<DVec3> {
381        (0..self.sides)
382            .map(|i| {
383                let a = TAU * i as f64 / self.sides as f64;
384                DVec3::new(a.cos() * self.radius, a.sin() * self.radius, 0.0)
385            })
386            .collect()
387    }
388    pub fn outer_circle(&self) -> Circle {
389        Circle::new(self.radius)
390    }
391    pub fn inner_circle(&self) -> Circle {
392        Circle::new(self.radius * (PI / self.sides as f64).cos())
393    }
394}
395impl Aabb for RegularPolygon {
396    fn aabb(&self) -> [DVec3; 2] {
397        self.points().aabb()
398    }
399}
400impl Opacity for RegularPolygon {
401    fn set_opacity(&mut self, opacity: f32) -> &mut Self {
402        self.fill_rgba = self.fill_rgba.with_alpha(opacity);
403        self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
404        self
405    }
406}
407impl FillColor for RegularPolygon {
408    fn fill_color(&self) -> AlphaColor<Srgb> {
409        self.fill_rgba
410    }
411    fn set_fill_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
412        self.fill_rgba = color;
413        self
414    }
415    fn set_fill_opacity(&mut self, opacity: f32) -> &mut Self {
416        self.fill_rgba = self.fill_rgba.with_alpha(opacity);
417        self
418    }
419}
420impl StrokeColor for RegularPolygon {
421    fn stroke_color(&self) -> AlphaColor<Srgb> {
422        self.stroke_rgba
423    }
424    fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self {
425        self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
426        self
427    }
428    fn set_stroke_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
429        self.stroke_rgba = color;
430        self
431    }
432}
433impl From<RegularPolygon> for Polygon {
434    fn from(value: RegularPolygon) -> Self {
435        Polygon::new(value.points()).with(|x| {
436            x.fill_rgba = value.fill_rgba;
437            x.stroke_rgba = value.stroke_rgba;
438            x.stroke_width = value.stroke_width;
439        })
440    }
441}
442#[cfg(feature = "render")]
443impl Extract for RegularPolygon {
444    fn extract(&self, ctx: &mut ExtractContext<'_>) {
445        Polygon::from(self.clone()).extract(ctx);
446    }
447}