Skip to main content

ranim_items/vitem/
mod.rs

1//! Quadratic Bezier Concatenated Item
2//!
3//! VItem itself is composed with 3d bezier path segments, but when *ranim* renders VItem,
4//! it assumes that all points are in the same plane to calculate depth information.
5//! Which means that ranim actually renders a **projection** of the VItem onto a plane.
6//!
7//! The projection target plane has the initial basis and normal defined as `(DVec3::X, DVec3::Y)` and `DVec3::Z` respectively, and it contains the first point of the VItem.
8//!
9//! So the normal way to use a [`VItem`] is to make sure that all points are in the same plane, at this time the **projection** is equivalent to the VItem itself. Or you may break this, and let ranim renders the **projection** of it.
10// pub mod arrow;
11/// Geometry items
12pub mod geometry;
13/// Svg item
14pub mod svg;
15/// Simple text items
16#[cfg(feature = "typst")]
17#[cfg_attr(docsrs, doc(cfg(feature = "typst")))]
18pub mod text;
19/// Typst items
20#[cfg(feature = "typst")]
21#[cfg_attr(docsrs, doc(cfg(feature = "typst")))]
22pub mod typst;
23
24use color::{AlphaColor, Srgb, palette::css};
25use glam::{DVec3, Vec4, vec4};
26use ranim_core::anchor::Aabb;
27use ranim_core::core_item::CoreItem;
28use ranim_core::{Extract, color, glam};
29
30use ranim_core::{
31    components::{PointVec, VecResizeTrait, rgba::Rgba, vpoint::VPointVec, width::Width},
32    prelude::{Alignable, Empty, FillColor, Opacity, Partial, StrokeWidth},
33    traits::{PointsFunc, RotateTransform, ScaleTransform, ShiftTransform, StrokeColor},
34};
35
36/// A vectorized item.
37///
38/// It is built from four components:
39/// - [`VItem::vpoints`]: the vpoints of the item, see [`VPointVec`].
40/// - [`VItem::stroke_widths`]: the stroke widths of the item, see [`Width`].
41/// - [`VItem::stroke_rgbas`]: the stroke colors of the item, see [`Rgba`].
42/// - [`VItem::fill_rgbas`]: the fill colors of the item, see [`Rgba`].
43///
44/// You can construct a [`VItem`] from a list of VPoints, see [`VPointVec`]:
45///
46/// ```rust
47/// use ranim_core::glam::dvec3;
48/// use ranim_items::vitem::VItem;
49///
50/// let vitem = VItem::from_vpoints(vec![
51///     dvec3(0.0, 0.0, 0.0),
52///     dvec3(1.0, 0.0, 0.0),
53///     dvec3(0.5, 1.0, 0.0),
54/// ]);
55/// ```
56#[derive(Debug, Clone, PartialEq)]
57pub struct VItem {
58    /// The normal vector of the projection target plane.
59    /// If `None`, the normal will be derived from the points at render time.
60    pub normal: Option<DVec3>,
61    /// vpoints data
62    pub vpoints: VPointVec,
63    /// stroke widths
64    pub stroke_widths: PointVec<Width>,
65    /// stroke rgbas
66    pub stroke_rgbas: PointVec<Rgba>,
67    /// fill rgbas
68    pub fill_rgbas: PointVec<Rgba>,
69}
70
71impl ranim_core::traits::Interpolatable for VItem {
72    fn lerp(&self, target: &Self, t: f64) -> Self {
73        Self {
74            normal: match (self.normal, target.normal) {
75                (Some(a), Some(b)) => Some(a.lerp(b, t)),
76                (Some(a), None) => Some(a),
77                (None, Some(b)) => Some(b),
78                (None, None) => None,
79            },
80            vpoints: self.vpoints.lerp(&target.vpoints, t),
81            stroke_widths: self.stroke_widths.lerp(&target.stroke_widths, t),
82            stroke_rgbas: self.stroke_rgbas.lerp(&target.stroke_rgbas, t),
83            fill_rgbas: self.fill_rgbas.lerp(&target.fill_rgbas, t),
84        }
85    }
86}
87
88impl PointsFunc for VItem {
89    fn apply_points_func(&mut self, f: impl Fn(&mut [DVec3])) -> &mut Self {
90        self.vpoints.apply_points_func(f);
91        self
92    }
93}
94
95impl Aabb for VItem {
96    fn aabb(&self) -> [DVec3; 2] {
97        self.vpoints.aabb()
98    }
99}
100
101impl ShiftTransform for VItem {
102    fn shift(&mut self, shift: DVec3) -> &mut Self {
103        self.vpoints.shift(shift);
104        self
105    }
106}
107
108impl RotateTransform for VItem {
109    fn rotate_on_axis(&mut self, axis: DVec3, angle: f64) -> &mut Self {
110        self.vpoints.rotate_on_axis(axis, angle);
111        if let Some(ref mut n) = self.normal {
112            *n = DVec3::rotate_axis(*n, axis, angle);
113        }
114        self
115    }
116}
117
118impl ScaleTransform for VItem {
119    fn scale(&mut self, scale: DVec3) -> &mut Self {
120        self.vpoints.scale(scale);
121        self
122    }
123}
124
125// impl AffineTransform for VItem {
126//     fn affine_transform_at_point(&mut self, mat: DAffine3, origin: DVec3) -> &mut Self {
127//         self.vpoints.affine_transform_at_point(mat, origin);
128//         self
129//     }
130// }
131
132/// Default stroke width
133pub use ranim_core::core_item::vitem::DEFAULT_STROKE_WIDTH;
134
135impl VItem {
136    /// Close the VItem
137    pub fn close(&mut self) -> &mut Self {
138        if self.vpoints.last() != self.vpoints.first() && !self.vpoints.is_empty() {
139            let start = self.vpoints[0];
140            let end = self.vpoints[self.vpoints.len() - 1];
141            self.extend_vpoints(&[(start + end) / 2.0, start]);
142        }
143        self
144    }
145    /// Shrink to center
146    pub fn shrink(&mut self) -> &mut Self {
147        let bb = self.aabb();
148        self.vpoints.0 = vec![bb[1]; self.vpoints.len()];
149        self
150    }
151    /// Set the vpoints of the VItem
152    pub fn set_points(&mut self, vpoints: Vec<DVec3>) {
153        self.vpoints.0 = vpoints;
154    }
155    /// Get anchor points
156    pub fn get_anchor(&self, idx: usize) -> Option<&DVec3> {
157        self.vpoints.get(idx * 2)
158    }
159    /// Set the normal of the VItem's projection plane
160    pub fn with_normal(mut self, normal: DVec3) -> Self {
161        self.normal = Some(normal);
162        self
163    }
164    /// Set the normal of the VItem's projection plane
165    pub fn set_normal(&mut self, normal: DVec3) {
166        self.normal = Some(normal);
167    }
168    /// Construct a [`VItem`] form vpoints
169    pub fn from_vpoints(vpoints: Vec<DVec3>) -> Self {
170        let stroke_widths = vec![DEFAULT_STROKE_WIDTH.into(); vpoints.len().div_ceil(2)];
171        let stroke_rgbas = vec![vec4(1.0, 1.0, 1.0, 1.0).into(); vpoints.len().div_ceil(2)];
172        let fill_rgbas = vec![vec4(0.0, 0.0, 0.0, 0.0).into(); vpoints.len().div_ceil(2)];
173        Self {
174            normal: None,
175            vpoints: VPointVec(vpoints),
176            stroke_rgbas: stroke_rgbas.into(),
177            stroke_widths: stroke_widths.into(),
178            fill_rgbas: fill_rgbas.into(),
179        }
180    }
181    /// Extend vpoints of the VItem
182    pub fn extend_vpoints(&mut self, vpoints: &[DVec3]) {
183        self.vpoints.extend(vpoints.to_vec());
184
185        let len = self.vpoints.len();
186        self.fill_rgbas.resize_with_last(len.div_ceil(2));
187        self.stroke_rgbas.resize_with_last(len.div_ceil(2));
188        self.stroke_widths.resize_with_last(len.div_ceil(2));
189    }
190
191    pub(crate) fn get_render_points(&self) -> Vec<Vec4> {
192        self.vpoints
193            .iter()
194            .zip(self.vpoints.get_closepath_flags())
195            .map(|(p, f)| p.as_vec3().extend(f.into()))
196            .collect()
197    }
198    /// Put start and end on
199    pub fn put_start_and_end_on(&mut self, start: DVec3, end: DVec3) -> &mut Self {
200        self.vpoints.put_start_and_end_on(start, end);
201        self
202    }
203}
204
205impl From<VItem> for ranim_core::core_item::vitem::VItem {
206    fn from(value: VItem) -> Self {
207        Self {
208            normal: value.normal.map(|n| n.as_vec3()),
209            points: value.get_render_points(),
210            fill_rgbas: value.fill_rgbas.iter().cloned().collect(),
211            stroke_rgbas: value.stroke_rgbas.iter().cloned().collect(),
212            stroke_widths: value.stroke_widths.iter().cloned().collect(),
213        }
214    }
215}
216
217impl Extract for VItem {
218    type Target = CoreItem;
219    fn extract_into(&self, buf: &mut Vec<Self::Target>) {
220        ranim_core::core_item::vitem::VItem::from(self.clone()).extract_into(buf);
221    }
222}
223
224// MARK: Anim traits impl
225impl Alignable for VItem {
226    fn is_aligned(&self, other: &Self) -> bool {
227        self.vpoints.is_aligned(&other.vpoints)
228            && self.stroke_widths.is_aligned(&other.stroke_widths)
229            && self.stroke_rgbas.is_aligned(&other.stroke_rgbas)
230            && self.fill_rgbas.is_aligned(&other.fill_rgbas)
231    }
232    fn align_with(&mut self, other: &mut Self) {
233        self.vpoints.align_with(&mut other.vpoints);
234        let len = self.vpoints.len().div_ceil(2);
235        self.stroke_rgbas.resize_preserving_order(len);
236        other.stroke_rgbas.resize_preserving_order(len);
237        self.stroke_widths.resize_preserving_order(len);
238        other.stroke_widths.resize_preserving_order(len);
239        self.fill_rgbas.resize_preserving_order(len);
240        other.fill_rgbas.resize_preserving_order(len);
241    }
242}
243
244impl Opacity for VItem {
245    fn set_opacity(&mut self, opacity: f32) -> &mut Self {
246        self.stroke_rgbas.set_opacity(opacity);
247        self.fill_rgbas.set_opacity(opacity);
248        self
249    }
250}
251
252impl Partial for VItem {
253    fn get_partial(&self, range: std::ops::Range<f64>) -> Self {
254        let vpoints = self.vpoints.get_partial(range.clone());
255        let stroke_rgbas = self.stroke_rgbas.get_partial(range.clone());
256        let stroke_widths = self.stroke_widths.get_partial(range.clone());
257        let fill_rgbas = self.fill_rgbas.get_partial(range.clone());
258        Self {
259            normal: self.normal,
260            vpoints,
261            stroke_widths,
262            stroke_rgbas,
263            fill_rgbas,
264        }
265    }
266    fn get_partial_closed(&self, range: std::ops::Range<f64>) -> Self {
267        let mut partial = self.get_partial(range);
268        partial.close();
269        partial
270    }
271}
272
273impl Empty for VItem {
274    fn empty() -> Self {
275        Self {
276            normal: None,
277            vpoints: VPointVec(vec![DVec3::ZERO; 3]),
278            stroke_widths: vec![0.0.into(); 2].into(),
279            stroke_rgbas: vec![Vec4::ZERO.into(); 2].into(),
280            fill_rgbas: vec![Vec4::ZERO.into(); 2].into(),
281        }
282    }
283}
284
285impl FillColor for VItem {
286    fn fill_color(&self) -> AlphaColor<Srgb> {
287        self.fill_rgbas
288            .first()
289            .map(|&rgba| rgba.into())
290            .unwrap_or(css::WHITE)
291    }
292    fn set_fill_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
293        self.fill_rgbas
294            .iter_mut()
295            .for_each(|rgba| *rgba = color.into());
296        self
297    }
298    fn set_fill_opacity(&mut self, opacity: f32) -> &mut Self {
299        self.fill_rgbas.set_opacity(opacity);
300        self
301    }
302}
303
304impl StrokeColor for VItem {
305    fn stroke_color(&self) -> AlphaColor<Srgb> {
306        self.stroke_rgbas
307            .first()
308            .map(|&rgba| rgba.into())
309            .unwrap_or(css::WHITE)
310    }
311    fn set_stroke_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
312        self.stroke_rgbas
313            .iter_mut()
314            .for_each(|rgba| *rgba = color.into());
315        self
316    }
317    fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self {
318        self.stroke_rgbas.set_opacity(opacity);
319        self
320    }
321}
322
323impl StrokeWidth for VItem {
324    fn stroke_width(&self) -> f32 {
325        self.stroke_widths[0].0
326    }
327    fn apply_stroke_func(&mut self, f: impl for<'a> Fn(&'a mut [Width])) -> &mut Self {
328        f(self.stroke_widths.as_mut());
329        self
330    }
331}
332
333#[cfg(test)]
334mod tests {
335    use ranim_core::{
336        core_item::vitem::vitem_normal_from_points,
337        traits::{Empty, Interpolatable, RotateTransform},
338    };
339
340    use super::{VItem, geometry::Square};
341
342    #[test]
343    fn generated_vitems_derive_their_interpolated_plane() {
344        let source = VItem::from(Square::new(4.0));
345        assert!(source.normal.is_none());
346        assert!(VItem::empty().normal.is_none());
347
348        let mut target = source.clone();
349        target.rotate_on_y(std::f64::consts::PI / 6.0);
350        target.rotate_on_x(std::f64::consts::PI / 6.0);
351
352        let interpolated = source.lerp(&target, 0.4);
353        assert!(interpolated.normal.is_none());
354
355        let core_item = ranim_core::core_item::vitem::VItem::from(interpolated);
356        let normal = vitem_normal_from_points(&core_item.points);
357        let origin = core_item.points[0].truncate();
358        for point in &core_item.points {
359            let distance = (point.truncate() - origin).dot(normal).abs();
360            assert!(distance < 1e-5, "point is {distance} away from its plane");
361        }
362    }
363}