Skip to main content

ranim_items/vitem/geometry/
polygon.rs

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