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
16pub mod text;
17/// Typst items
18pub mod typst;
19
20use color::{AlphaColor, Srgb, palette::css};
21use glam::{DVec3, Vec4, vec4};
22use ranim_core::anchor::Aabb;
23use ranim_core::core_item::CoreItem;
24use ranim_core::{Extract, color, glam};
25
26use ranim_core::{
27    components::{PointVec, VecResizeTrait, rgba::Rgba, vpoint::VPointVec, width::Width},
28    prelude::{Alignable, Empty, FillColor, Opacity, Partial, StrokeWidth},
29    traits::{PointsFunc, RotateTransform, ScaleTransform, ShiftTransform, StrokeColor},
30};
31
32/// A vectorized item.
33///
34/// It is built from four components:
35/// - [`VItem::vpoints`]: the vpoints of the item, see [`VPointVec`].
36/// - [`VItem::stroke_widths`]: the stroke widths of the item, see [`Width`].
37/// - [`VItem::stroke_rgbas`]: the stroke colors of the item, see [`Rgba`].
38/// - [`VItem::fill_rgbas`]: the fill colors of the item, see [`Rgba`].
39///
40/// You can construct a [`VItem`] from a list of VPoints, see [`VPointVec`]:
41///
42/// ```rust
43/// let vitem = VItem::from_vpoints(vec![
44///     dvec3(0.0, 0.0, 0.0),
45///     dvec3(1.0, 0.0, 0.0),
46///     dvec3(0.5, 1.0, 0.0),
47/// ]);
48/// ```
49#[derive(Debug, Clone, PartialEq)]
50pub struct VItem {
51    /// The normal vector of the projection target plane.
52    /// If `None`, the normal will be computed from the first three points at render time.
53    pub normal: Option<DVec3>,
54    /// vpoints data
55    pub vpoints: VPointVec,
56    /// stroke widths
57    pub stroke_widths: PointVec<Width>,
58    /// stroke rgbas
59    pub stroke_rgbas: PointVec<Rgba>,
60    /// fill rgbas
61    pub fill_rgbas: PointVec<Rgba>,
62}
63
64impl ranim_core::traits::Interpolatable for VItem {
65    fn lerp(&self, target: &Self, t: f64) -> Self {
66        Self {
67            normal: match (self.normal, target.normal) {
68                (Some(a), Some(b)) => Some(a.lerp(b, t)),
69                (Some(a), None) => Some(a),
70                (None, Some(b)) => Some(b),
71                (None, None) => None,
72            },
73            vpoints: self.vpoints.lerp(&target.vpoints, t),
74            stroke_widths: self.stroke_widths.lerp(&target.stroke_widths, t),
75            stroke_rgbas: self.stroke_rgbas.lerp(&target.stroke_rgbas, t),
76            fill_rgbas: self.fill_rgbas.lerp(&target.fill_rgbas, t),
77        }
78    }
79}
80
81impl PointsFunc for VItem {
82    fn apply_points_func(&mut self, f: impl Fn(&mut [DVec3])) -> &mut Self {
83        self.vpoints.apply_points_func(f);
84        self
85    }
86}
87
88impl Aabb for VItem {
89    fn aabb(&self) -> [DVec3; 2] {
90        self.vpoints.aabb()
91    }
92}
93
94impl ShiftTransform for VItem {
95    fn shift(&mut self, shift: DVec3) -> &mut Self {
96        self.vpoints.shift(shift);
97        self
98    }
99}
100
101impl RotateTransform for VItem {
102    fn rotate_on_axis(&mut self, axis: DVec3, angle: f64) -> &mut Self {
103        self.vpoints.rotate_on_axis(axis, angle);
104        if let Some(ref mut n) = self.normal {
105            *n = DVec3::rotate_axis(*n, axis, angle);
106        }
107        self
108    }
109}
110
111impl ScaleTransform for VItem {
112    fn scale(&mut self, scale: DVec3) -> &mut Self {
113        self.vpoints.scale(scale);
114        self
115    }
116}
117
118// impl AffineTransform for VItem {
119//     fn affine_transform_at_point(&mut self, mat: DAffine3, origin: DVec3) -> &mut Self {
120//         self.vpoints.affine_transform_at_point(mat, origin);
121//         self
122//     }
123// }
124
125/// Default stroke width
126pub use ranim_core::core_item::vitem::DEFAULT_STROKE_WIDTH;
127
128impl VItem {
129    /// Close the VItem
130    pub fn close(&mut self) -> &mut Self {
131        if self.vpoints.last() != self.vpoints.first() && !self.vpoints.is_empty() {
132            let start = self.vpoints[0];
133            let end = self.vpoints[self.vpoints.len() - 1];
134            self.extend_vpoints(&[(start + end) / 2.0, start]);
135        }
136        self
137    }
138    /// Shrink to center
139    pub fn shrink(&mut self) -> &mut Self {
140        let bb = self.aabb();
141        self.vpoints.0 = vec![bb[1]; self.vpoints.len()];
142        self
143    }
144    /// Set the vpoints of the VItem
145    pub fn set_points(&mut self, vpoints: Vec<DVec3>) {
146        self.vpoints.0 = vpoints;
147    }
148    /// Get anchor points
149    pub fn get_anchor(&self, idx: usize) -> Option<&DVec3> {
150        self.vpoints.get(idx * 2)
151    }
152    /// Set the normal of the VItem's projection plane
153    pub fn with_normal(mut self, normal: DVec3) -> Self {
154        self.normal = Some(normal);
155        self
156    }
157    /// Set the normal of the VItem's projection plane
158    pub fn set_normal(&mut self, normal: DVec3) {
159        self.normal = Some(normal);
160    }
161    /// Construct a [`VItem`] form vpoints
162    pub fn from_vpoints(vpoints: Vec<DVec3>) -> Self {
163        let stroke_widths = vec![DEFAULT_STROKE_WIDTH.into(); vpoints.len().div_ceil(2)];
164        let stroke_rgbas = vec![vec4(1.0, 1.0, 1.0, 1.0).into(); vpoints.len().div_ceil(2)];
165        let fill_rgbas = vec![vec4(0.0, 0.0, 0.0, 0.0).into(); vpoints.len().div_ceil(2)];
166        Self {
167            normal: None,
168            vpoints: VPointVec(vpoints),
169            stroke_rgbas: stroke_rgbas.into(),
170            stroke_widths: stroke_widths.into(),
171            fill_rgbas: fill_rgbas.into(),
172        }
173    }
174    /// Extend vpoints of the VItem
175    pub fn extend_vpoints(&mut self, vpoints: &[DVec3]) {
176        self.vpoints.extend(vpoints.to_vec());
177
178        let len = self.vpoints.len();
179        self.fill_rgbas.resize_with_last(len.div_ceil(2));
180        self.stroke_rgbas.resize_with_last(len.div_ceil(2));
181        self.stroke_widths.resize_with_last(len.div_ceil(2));
182    }
183
184    pub(crate) fn get_render_points(&self) -> Vec<Vec4> {
185        self.vpoints
186            .iter()
187            .zip(self.vpoints.get_closepath_flags())
188            .map(|(p, f)| p.as_vec3().extend(f.into()))
189            .collect()
190    }
191    /// Put start and end on
192    pub fn put_start_and_end_on(&mut self, start: DVec3, end: DVec3) -> &mut Self {
193        self.vpoints.put_start_and_end_on(start, end);
194        self
195    }
196}
197
198impl From<VItem> for ranim_core::core_item::vitem::VItem {
199    fn from(value: VItem) -> Self {
200        Self {
201            normal: value.normal.map(|n| n.as_vec3()),
202            points: value.get_render_points(),
203            fill_rgbas: value.fill_rgbas.iter().cloned().collect(),
204            stroke_rgbas: value.stroke_rgbas.iter().cloned().collect(),
205            stroke_widths: value.stroke_widths.iter().cloned().collect(),
206        }
207    }
208}
209
210impl Extract for VItem {
211    type Target = CoreItem;
212    fn extract_into(&self, buf: &mut Vec<Self::Target>) {
213        ranim_core::core_item::vitem::VItem::from(self.clone()).extract_into(buf);
214    }
215}
216
217// MARK: Anim traits impl
218impl Alignable for VItem {
219    fn is_aligned(&self, other: &Self) -> bool {
220        self.vpoints.is_aligned(&other.vpoints)
221            && self.stroke_widths.is_aligned(&other.stroke_widths)
222            && self.stroke_rgbas.is_aligned(&other.stroke_rgbas)
223            && self.fill_rgbas.is_aligned(&other.fill_rgbas)
224    }
225    fn align_with(&mut self, other: &mut Self) {
226        self.vpoints.align_with(&mut other.vpoints);
227        let len = self.vpoints.len().div_ceil(2);
228        self.stroke_rgbas.resize_preserving_order(len);
229        other.stroke_rgbas.resize_preserving_order(len);
230        self.stroke_widths.resize_preserving_order(len);
231        other.stroke_widths.resize_preserving_order(len);
232        self.fill_rgbas.resize_preserving_order(len);
233        other.fill_rgbas.resize_preserving_order(len);
234    }
235}
236
237impl Opacity for VItem {
238    fn set_opacity(&mut self, opacity: f32) -> &mut Self {
239        self.stroke_rgbas.set_opacity(opacity);
240        self.fill_rgbas.set_opacity(opacity);
241        self
242    }
243}
244
245impl Partial for VItem {
246    fn get_partial(&self, range: std::ops::Range<f64>) -> Self {
247        let vpoints = self.vpoints.get_partial(range.clone());
248        let stroke_rgbas = self.stroke_rgbas.get_partial(range.clone());
249        let stroke_widths = self.stroke_widths.get_partial(range.clone());
250        let fill_rgbas = self.fill_rgbas.get_partial(range.clone());
251        Self {
252            normal: self.normal,
253            vpoints,
254            stroke_widths,
255            stroke_rgbas,
256            fill_rgbas,
257        }
258    }
259    fn get_partial_closed(&self, range: std::ops::Range<f64>) -> Self {
260        let mut partial = self.get_partial(range);
261        partial.close();
262        partial
263    }
264}
265
266impl Empty for VItem {
267    fn empty() -> Self {
268        Self {
269            normal: Some(DVec3::Z),
270            vpoints: VPointVec(vec![DVec3::ZERO; 3]),
271            stroke_widths: vec![0.0.into(); 2].into(),
272            stroke_rgbas: vec![Vec4::ZERO.into(); 2].into(),
273            fill_rgbas: vec![Vec4::ZERO.into(); 2].into(),
274        }
275    }
276}
277
278impl FillColor for VItem {
279    fn fill_color(&self) -> AlphaColor<Srgb> {
280        self.fill_rgbas
281            .first()
282            .map(|&rgba| rgba.into())
283            .unwrap_or(css::WHITE)
284    }
285    fn set_fill_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
286        self.fill_rgbas
287            .iter_mut()
288            .for_each(|rgba| *rgba = color.into());
289        self
290    }
291    fn set_fill_opacity(&mut self, opacity: f32) -> &mut Self {
292        self.fill_rgbas.set_opacity(opacity);
293        self
294    }
295}
296
297impl StrokeColor for VItem {
298    fn stroke_color(&self) -> AlphaColor<Srgb> {
299        self.stroke_rgbas
300            .first()
301            .map(|&rgba| rgba.into())
302            .unwrap_or(css::WHITE)
303    }
304    fn set_stroke_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
305        self.stroke_rgbas
306            .iter_mut()
307            .for_each(|rgba| *rgba = color.into());
308        self
309    }
310    fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self {
311        self.stroke_rgbas.set_opacity(opacity);
312        self
313    }
314}
315
316impl StrokeWidth for VItem {
317    fn stroke_width(&self) -> f32 {
318        self.stroke_widths[0].0
319    }
320    fn apply_stroke_func(&mut self, f: impl for<'a> Fn(&'a mut [Width])) -> &mut Self {
321        f(self.stroke_widths.as_mut());
322        self
323    }
324}