Skip to main content

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