ranim_items/vitem/geometry/
polygon.rs

1use std::f64::consts::{PI, TAU};
2
3use ranim_core::{
4    Extract,
5    anchor::{Aabb, AabbPoint, Locate},
6    color,
7    core_item::CoreItem,
8    glam::{DVec2, DVec3, dvec2, dvec3},
9    traits::{Discard, RotateTransform, ScaleTransform, ShiftTransform, ShiftTransformExt},
10};
11
12use color::{AlphaColor, Srgb};
13use itertools::Itertools;
14
15use crate::vitem::{DEFAULT_STROKE_WIDTH, VItem, geometry::Circle};
16use ranim_core::core_item::vitem::Basis2d;
17use ranim_core::traits::{Alignable, FillColor, Opacity, StrokeColor, StrokeWidth, With};
18
19// MARK: ### Square ###
20/// A Square
21#[derive(Clone, Debug, ranim_macros::Interpolatable)]
22pub struct Square {
23    /// Basis
24    pub basis: Basis2d,
25    /// Center
26    pub center: DVec3,
27    /// Size
28    pub size: f64,
29
30    /// Stroke rgba
31    pub stroke_rgba: AlphaColor<Srgb>,
32    /// Stroke width
33    pub stroke_width: f32,
34    /// Fill rgba
35    pub fill_rgba: AlphaColor<Srgb>,
36}
37
38impl Square {
39    /// Constructor
40    pub fn new(size: f64) -> Self {
41        Self {
42            basis: Basis2d::default(),
43            center: dvec3(0.0, 0.0, 0.0),
44            size,
45
46            stroke_rgba: AlphaColor::WHITE,
47            stroke_width: DEFAULT_STROKE_WIDTH,
48            fill_rgba: AlphaColor::TRANSPARENT,
49        }
50    }
51    /// Scale the square by the given scale, with the given anchor as the center.
52    ///
53    /// Note that this accepts a `f64` scale dispite of [`ScaleTransform`]'s `DVec3`,
54    /// because this keeps the square a square.
55    pub fn scale(&mut self, scale: f64) -> &mut Self {
56        self.scale_at(scale, AabbPoint::CENTER)
57    }
58    /// Scale the square by the given scale, with the given anchor as the center.
59    ///
60    /// Note that this accepts a `f64` scale dispite of [`ScaleTransform`]'s `DVec3`,
61    /// because this keeps the square a square.
62    pub fn scale_at<T>(&mut self, scale: f64, anchor: T) -> &mut Self
63    where
64        T: Locate<Self>,
65    {
66        let anchor = anchor.locate(self);
67        self.size *= scale;
68        self.center
69            .shift(-anchor)
70            .scale(DVec3::splat(scale))
71            .shift(anchor);
72        self
73    }
74}
75
76// MARK: Traits impl
77impl Aabb for Square {
78    fn aabb(&self) -> [DVec3; 2] {
79        let (u, v) = self.basis.uv();
80        [
81            self.center + self.size / 2.0 * (u + v),
82            self.center - self.size / 2.0 * (u + v),
83        ]
84        .aabb()
85    }
86}
87
88impl ShiftTransform for Square {
89    fn shift(&mut self, shift: DVec3) -> &mut Self {
90        self.center.shift(shift);
91        self
92    }
93}
94
95impl RotateTransform for Square {
96    fn rotate_on_axis(&mut self, axis: DVec3, angle: f64) -> &mut Self {
97        self.center.rotate_on_axis(axis, angle);
98        self.basis.rotate_on_axis(axis, angle);
99        self
100    }
101}
102
103impl Alignable for Square {
104    fn is_aligned(&self, _other: &Self) -> bool {
105        true
106    }
107    fn align_with(&mut self, _other: &mut Self) {}
108}
109
110impl Opacity for Square {
111    fn set_opacity(&mut self, opacity: f32) -> &mut Self {
112        self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
113        self.fill_rgba = self.fill_rgba.with_alpha(opacity);
114        self
115    }
116}
117
118impl StrokeColor for Square {
119    fn stroke_color(&self) -> AlphaColor<Srgb> {
120        self.stroke_rgba
121    }
122    fn set_stroke_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
123        self.stroke_rgba = color;
124        self
125    }
126    fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self {
127        self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
128        self
129    }
130}
131
132impl FillColor for Square {
133    fn fill_color(&self) -> AlphaColor<Srgb> {
134        self.fill_rgba
135    }
136    fn set_fill_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
137        self.fill_rgba = color;
138        self
139    }
140    fn set_fill_opacity(&mut self, opacity: f32) -> &mut Self {
141        self.fill_rgba = self.fill_rgba.with_alpha(opacity);
142        self
143    }
144}
145
146impl Extract for Square {
147    type Target = CoreItem;
148    fn extract_into(&self, buf: &mut Vec<Self::Target>) {
149        VItem::from(self.clone()).extract_into(buf);
150    }
151}
152
153// MARK: Conversions
154impl From<Square> for Rectangle {
155    fn from(value: Square) -> Self {
156        let Square {
157            basis,
158            center,
159            size: width,
160            stroke_rgba,
161            stroke_width,
162            fill_rgba,
163        } = value;
164        let (u, v) = basis.uv();
165        let p0 = center - width / 2.0 * u - width / 2.0 * v;
166        Rectangle {
167            basis,
168            p0,
169            size: dvec2(width, width),
170            stroke_rgba,
171            stroke_width,
172            fill_rgba,
173        }
174    }
175}
176
177impl From<Square> for RegularPolygon {
178    fn from(value: Square) -> Self {
179        RegularPolygon::new(4, value.size / 2.0 * 2.0f64.sqrt()).with(|x| {
180            x.basis = value.basis;
181            x.stroke_rgba = value.stroke_rgba;
182            x.stroke_width = value.stroke_width;
183            x.fill_rgba = value.fill_rgba;
184        })
185    }
186}
187
188impl From<Square> for Polygon {
189    fn from(value: Square) -> Self {
190        Rectangle::from(value).into()
191    }
192}
193
194impl From<Square> for VItem {
195    fn from(value: Square) -> Self {
196        Rectangle::from(value).into()
197    }
198}
199
200// MARK: ### Rectangle ###
201/// Rectangle
202#[derive(Clone, Debug, ranim_macros::Interpolatable)]
203pub struct Rectangle {
204    /// Basis info
205    pub basis: Basis2d,
206    /// Bottom left corner (minimum)
207    pub p0: DVec3,
208    /// Width and height
209    pub size: DVec2,
210
211    /// Stroke rgba
212    pub stroke_rgba: AlphaColor<Srgb>,
213    /// Stroke width
214    pub stroke_width: f32,
215    /// Fill rgba
216    pub fill_rgba: AlphaColor<Srgb>,
217}
218
219impl Rectangle {
220    /// Constructor
221    pub fn new(width: f64, height: f64) -> Self {
222        let half_width = width / 2.0;
223        let half_height = height / 2.0;
224        let p0 = dvec3(-half_width, -half_height, 0.0);
225        let size = dvec2(width, height);
226        Self::from_min_size(p0, size)
227    }
228    /// Construct a rectangle from the bottom-left point (minimum) and size.
229    pub fn from_min_size(p0: DVec3, size: DVec2) -> Self {
230        Self {
231            basis: Basis2d::default(),
232            p0,
233            size,
234            stroke_rgba: AlphaColor::WHITE,
235            stroke_width: DEFAULT_STROKE_WIDTH,
236            fill_rgba: AlphaColor::TRANSPARENT,
237        }
238    }
239    /// Width
240    pub fn width(&self) -> f64 {
241        self.size.x.abs()
242    }
243    /// Height
244    pub fn height(&self) -> f64 {
245        self.size.y.abs()
246    }
247}
248
249// MARK: Traits impl
250impl Aabb for Rectangle {
251    fn aabb(&self) -> [DVec3; 2] {
252        let (u, v) = self.basis.uv();
253        let p1 = self.p0;
254        let p2 = self.p0 + self.size.x * u + self.size.y * v;
255        [p1, p2].aabb()
256    }
257}
258
259impl ShiftTransform for Rectangle {
260    fn shift(&mut self, shift: DVec3) -> &mut Self {
261        self.p0.shift(shift);
262        self
263    }
264}
265
266impl RotateTransform for Rectangle {
267    fn rotate_on_axis(&mut self, axis: DVec3, angle: f64) -> &mut Self {
268        self.p0.rotate_on_axis(axis, angle);
269        self.basis.rotate_on_axis(axis, angle);
270        self
271    }
272}
273
274impl ScaleTransform for Rectangle {
275    fn scale(&mut self, scale: DVec3) -> &mut Self {
276        self.p0.scale(scale);
277        let (u, v) = self.basis.uv();
278        let scale_u = scale.dot(u);
279        let scale_v = scale.dot(v);
280        self.size *= dvec2(scale_u, scale_v);
281        self
282    }
283}
284
285impl Opacity for Rectangle {
286    fn set_opacity(&mut self, opacity: f32) -> &mut Self {
287        self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
288        self.fill_rgba = self.fill_rgba.with_alpha(opacity);
289        self
290    }
291}
292
293impl Alignable for Rectangle {
294    fn align_with(&mut self, _other: &mut Self) {}
295    fn is_aligned(&self, _other: &Self) -> bool {
296        true
297    }
298}
299
300impl StrokeColor for Rectangle {
301    fn stroke_color(&self) -> AlphaColor<Srgb> {
302        self.stroke_rgba
303    }
304    fn set_stroke_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
305        self.stroke_rgba = color;
306        self
307    }
308    fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self {
309        self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
310        self
311    }
312}
313
314impl FillColor for Rectangle {
315    fn fill_color(&self) -> AlphaColor<Srgb> {
316        self.fill_rgba
317    }
318    fn set_fill_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
319        self.fill_rgba = color;
320        self
321    }
322    fn set_fill_opacity(&mut self, opacity: f32) -> &mut Self {
323        self.fill_rgba = self.fill_rgba.with_alpha(opacity);
324        self
325    }
326}
327
328// MARK: Conversions
329impl From<Rectangle> for Polygon {
330    fn from(value: Rectangle) -> Self {
331        let p0 = value.p0;
332        let (u, v) = value.basis.uv();
333        let DVec2 { x: w, y: h } = value.size;
334        let points = vec![p0, p0 + u * w, p0 + u * w + v * h, p0 + v * h];
335        Polygon {
336            basis: value.basis,
337            points,
338            stroke_rgba: value.stroke_rgba,
339            stroke_width: value.stroke_width,
340            fill_rgba: value.fill_rgba,
341        }
342    }
343}
344
345impl From<Rectangle> for VItem {
346    fn from(value: Rectangle) -> Self {
347        Polygon::from(value).into()
348    }
349}
350
351impl Extract for Rectangle {
352    type Target = CoreItem;
353    fn extract_into(&self, buf: &mut Vec<Self::Target>) {
354        VItem::from(self.clone()).extract_into(buf);
355    }
356}
357
358// MARK: ### Polygon ###
359/// A Polygon with uniform stroke and fill
360#[derive(Clone, Debug, ranim_macros::Interpolatable)]
361pub struct Polygon {
362    /// Basis info
363    pub basis: Basis2d,
364    /// Corner points
365    pub points: Vec<DVec3>,
366    /// Stroke rgba
367    pub stroke_rgba: AlphaColor<Srgb>,
368    /// Stroke width
369    pub stroke_width: f32,
370    /// Fill rgba
371    pub fill_rgba: AlphaColor<Srgb>,
372}
373
374impl Polygon {
375    /// Constructor
376    pub fn new(points: Vec<DVec3>) -> Self {
377        Self {
378            basis: Basis2d::default(),
379            points,
380            stroke_rgba: AlphaColor::WHITE,
381            stroke_width: DEFAULT_STROKE_WIDTH,
382            fill_rgba: AlphaColor::TRANSPARENT,
383        }
384    }
385}
386
387// MARK: Traits impl
388impl Aabb for Polygon {
389    fn aabb(&self) -> [DVec3; 2] {
390        self.points.aabb()
391    }
392}
393
394impl ShiftTransform for Polygon {
395    fn shift(&mut self, shift: DVec3) -> &mut Self {
396        self.points.shift(shift);
397        self
398    }
399}
400
401impl RotateTransform for Polygon {
402    fn rotate_on_axis(&mut self, axis: DVec3, angle: f64) -> &mut Self {
403        self.points.rotate_on_axis(axis, angle);
404        self.basis.rotate_on_axis(axis, angle);
405        self
406    }
407}
408
409impl ScaleTransform for Polygon {
410    fn scale(&mut self, scale: DVec3) -> &mut Self {
411        self.points.scale(scale);
412        self
413    }
414}
415
416// impl AffineTransform for Polygon {
417//     fn affine_transform_at_point(&mut self, mat: DAffine3, origin: DVec3) -> &mut Self {
418//         self.points.affine_transform_at_point(mat, origin);
419//         // TODO: how to transform basis?
420//         self
421//     }
422// }
423
424impl Alignable for Polygon {
425    fn is_aligned(&self, other: &Self) -> bool {
426        self.points.len() == other.points.len()
427    }
428    fn align_with(&mut self, other: &mut Self) {
429        if self.points.len() > other.points.len() {
430            return other.align_with(self);
431        }
432        // TODO: find a better algo to minimize the distance
433        self.points
434            .resize(other.points.len(), self.points.last().cloned().unwrap());
435    }
436}
437
438impl Opacity for Polygon {
439    fn set_opacity(&mut self, opacity: f32) -> &mut Self {
440        self.fill_rgba = self.fill_rgba.with_alpha(opacity);
441        self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
442        self
443    }
444}
445
446impl StrokeColor for Polygon {
447    fn stroke_color(&self) -> AlphaColor<Srgb> {
448        self.stroke_rgba
449    }
450    fn set_stroke_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
451        self.stroke_rgba = color;
452        self
453    }
454    fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self {
455        self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
456        self
457    }
458}
459
460impl FillColor for Polygon {
461    fn fill_color(&self) -> AlphaColor<Srgb> {
462        self.fill_rgba
463    }
464    fn set_fill_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
465        self.fill_rgba = color;
466        self
467    }
468    fn set_fill_opacity(&mut self, opacity: f32) -> &mut Self {
469        self.fill_rgba = self.fill_rgba.with_alpha(opacity);
470        self
471    }
472}
473
474// MARK: Conversions
475impl From<Polygon> for VItem {
476    fn from(value: Polygon) -> Self {
477        let Polygon {
478            mut points,
479            stroke_rgba,
480            stroke_width,
481            fill_rgba,
482            basis,
483            ..
484        } = value;
485        assert!(points.len() > 2);
486
487        // Close the polygon
488        points.push(points[0]);
489
490        let anchors = points;
491        let handles = anchors
492            .iter()
493            .tuple_windows()
494            .map(|(&a, &b)| 0.5 * (a + b))
495            .collect::<Vec<_>>();
496
497        // Interleave anchors and handles
498        let vpoints = anchors.into_iter().interleave(handles).collect::<Vec<_>>();
499        VItem::from_vpoints(vpoints)
500            .with_basis(basis)
501            .with(|vitem| {
502                vitem
503                    .set_fill_color(fill_rgba)
504                    .set_stroke_color(stroke_rgba)
505                    .set_stroke_width(stroke_width);
506            })
507    }
508}
509
510impl Extract for Polygon {
511    type Target = CoreItem;
512    fn extract_into(&self, buf: &mut Vec<Self::Target>) {
513        VItem::from(self.clone()).extract_into(buf);
514    }
515}
516
517#[derive(Debug, Clone, ranim_macros::Interpolatable)]
518/// A regular polygon.
519pub struct RegularPolygon {
520    /// Local coordinate system
521    pub basis: Basis2d,
522    /// Center of the polygon
523    pub center: DVec3,
524    /// Number of sides
525    pub sides: usize,
526    /// Radius of the polygon (i.e. distance from center to a vertex)
527    pub radius: f64,
528    /// Stroke rgba
529    pub stroke_rgba: AlphaColor<Srgb>,
530    /// Stroke width
531    pub stroke_width: f32,
532    /// Fill rgba
533    pub fill_rgba: AlphaColor<Srgb>,
534}
535
536impl Alignable for RegularPolygon {
537    fn is_aligned(&self, _other: &Self) -> bool {
538        true
539    }
540    fn align_with(&mut self, _other: &mut Self) {}
541}
542
543impl RegularPolygon {
544    /// Creates a new regular polygon.
545    pub fn new(sides: usize, radius: f64) -> Self {
546        assert!(sides >= 3);
547        Self {
548            basis: Basis2d::default(),
549            center: DVec3::ZERO,
550            sides,
551            radius,
552            stroke_rgba: AlphaColor::WHITE,
553            stroke_width: DEFAULT_STROKE_WIDTH,
554            fill_rgba: AlphaColor::TRANSPARENT,
555        }
556    }
557    /// Returns the vertices of the polygon.
558    pub fn points(&self) -> Vec<DVec3> {
559        let &Self {
560            sides,
561            radius,
562            center,
563            ..
564        } = self;
565        let u = self.basis.u();
566        let normal = self.basis.normal();
567        (0..sides)
568            .map(|i| TAU * (i as f64 / sides as f64))
569            .map(|angle| u.rotate_axis(normal, angle) * radius + center)
570            .collect()
571    }
572    /// Returns the outer circle of the polygon.
573    pub fn outer_circle(&self) -> Circle {
574        Circle::new(self.radius).with(|x| x.move_to(self.center).discard())
575    }
576    /// Returns the inner circle of the polygon.
577    pub fn inner_circle(&self) -> Circle {
578        Circle::new(self.radius * (PI / self.sides as f64).cos())
579            .with(|x| x.move_to(self.center).discard())
580    }
581}
582
583impl Aabb for RegularPolygon {
584    fn aabb(&self) -> [DVec3; 2] {
585        self.points().aabb()
586    }
587}
588
589impl ShiftTransform for RegularPolygon {
590    fn shift(&mut self, offset: DVec3) -> &mut Self {
591        self.center.shift(offset);
592        self
593    }
594}
595
596impl RotateTransform for RegularPolygon {
597    fn rotate_on_axis(&mut self, axis: DVec3, angle: f64) -> &mut Self {
598        self.basis.rotate_on_axis(axis, angle);
599        self.center.rotate_on_axis(axis, angle);
600        self
601    }
602}
603
604impl Opacity for RegularPolygon {
605    fn set_opacity(&mut self, opacity: f32) -> &mut Self {
606        self.fill_rgba = self.fill_rgba.with_alpha(opacity);
607        self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
608        self
609    }
610}
611
612impl FillColor for RegularPolygon {
613    fn fill_color(&self) -> AlphaColor<Srgb> {
614        self.fill_rgba
615    }
616
617    fn set_fill_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
618        self.fill_rgba = color;
619        self
620    }
621
622    fn set_fill_opacity(&mut self, opacity: f32) -> &mut Self {
623        self.fill_rgba = self.fill_rgba.with_alpha(opacity);
624        self
625    }
626}
627
628impl StrokeColor for RegularPolygon {
629    fn stroke_color(&self) -> AlphaColor<Srgb> {
630        self.stroke_rgba
631    }
632
633    fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self {
634        self.stroke_rgba = self.stroke_rgba.with_alpha(opacity);
635        self
636    }
637
638    fn set_stroke_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
639        self.stroke_rgba = color;
640        self
641    }
642}
643
644impl From<RegularPolygon> for Polygon {
645    fn from(value: RegularPolygon) -> Self {
646        Polygon::new(value.points()).with(|x| {
647            x.basis = value.basis;
648            x.fill_rgba = value.fill_rgba;
649            x.stroke_rgba = value.stroke_rgba;
650            x.stroke_width = value.stroke_width;
651        })
652    }
653}
654
655impl Extract for RegularPolygon {
656    type Target = CoreItem;
657
658    fn extract_into(&self, buf: &mut Vec<Self::Target>) {
659        Polygon::from(self.clone()).extract_into(buf);
660    }
661}