1pub mod geometry;
13pub mod svg;
15#[cfg(feature = "typst")]
17#[cfg_attr(docsrs, doc(cfg(feature = "typst")))]
18pub mod text;
19#[cfg(feature = "typst")]
21#[cfg_attr(docsrs, doc(cfg(feature = "typst")))]
22pub mod typst;
23
24use color::{AlphaColor, Srgb, palette::css};
25use glam::{DVec3, Mat4, 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::{ApplyTransform, PointsFunc, StrokeColor},
34};
35
36#[derive(Debug, Clone, PartialEq)]
57pub struct VItem {
58 pub normal: Option<DVec3>,
61 pub vpoints: VPointVec,
63 pub stroke_widths: PointVec<Width>,
65 pub stroke_rgbas: PointVec<Rgba>,
67 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<G: Into<glam::DAffine3>> ApplyTransform<G> for VItem {
102 fn apply(&mut self, transform: G) -> &mut Self {
103 let transform = transform.into();
104 self.vpoints.apply(transform);
105 if let Some(ref mut normal) = self.normal {
106 let matrix = transform.matrix3;
107 let transformed = if matrix.determinant().abs() > 1e-12 {
108 matrix.inverse().transpose() * *normal
109 } else {
110 matrix * *normal
111 };
112 if let Some(unit) = transformed.try_normalize() {
113 *normal = unit;
114 }
115 }
116 self
117 }
118}
119
120pub use ranim_core::core_item::vitem::DEFAULT_STROKE_WIDTH;
122
123impl VItem {
124 pub fn close(&mut self) -> &mut Self {
126 if self.vpoints.last() != self.vpoints.first() && !self.vpoints.is_empty() {
127 let start = self.vpoints[0];
128 let end = self.vpoints[self.vpoints.len() - 1];
129 self.extend_vpoints(&[(start + end) / 2.0, start]);
130 }
131 self
132 }
133 pub fn shrink(&mut self) -> &mut Self {
135 let bb = self.aabb();
136 self.vpoints.0 = vec![bb[1]; self.vpoints.len()];
137 self
138 }
139 pub fn set_points(&mut self, vpoints: Vec<DVec3>) {
141 self.vpoints.0 = vpoints;
142 }
143 pub fn get_anchor(&self, idx: usize) -> Option<&DVec3> {
145 self.vpoints.get(idx * 2)
146 }
147 pub fn with_normal(mut self, normal: DVec3) -> Self {
149 self.normal = Some(normal);
150 self
151 }
152 pub fn set_normal(&mut self, normal: DVec3) {
154 self.normal = Some(normal);
155 }
156 pub fn from_vpoints(vpoints: Vec<DVec3>) -> Self {
158 let stroke_widths = vec![DEFAULT_STROKE_WIDTH.into(); vpoints.len().div_ceil(2)];
159 let stroke_rgbas = vec![vec4(1.0, 1.0, 1.0, 1.0).into(); vpoints.len().div_ceil(2)];
160 let fill_rgbas = vec![vec4(0.0, 0.0, 0.0, 0.0).into(); vpoints.len().div_ceil(2)];
161 Self {
162 normal: None,
163 vpoints: VPointVec(vpoints),
164 stroke_rgbas: stroke_rgbas.into(),
165 stroke_widths: stroke_widths.into(),
166 fill_rgbas: fill_rgbas.into(),
167 }
168 }
169 pub fn extend_vpoints(&mut self, vpoints: &[DVec3]) {
171 self.vpoints.extend(vpoints.to_vec());
172
173 let len = self.vpoints.len();
174 self.fill_rgbas.resize_with_last(len.div_ceil(2));
175 self.stroke_rgbas.resize_with_last(len.div_ceil(2));
176 self.stroke_widths.resize_with_last(len.div_ceil(2));
177 }
178
179 pub(crate) fn get_render_points(&self) -> Vec<Vec4> {
180 self.vpoints
181 .iter()
182 .zip(self.vpoints.get_closepath_flags())
183 .map(|(p, f)| p.as_vec3().extend(f.into()))
184 .collect()
185 }
186 pub fn put_start_and_end_on(&mut self, start: DVec3, end: DVec3) -> &mut Self {
188 self.vpoints.put_start_and_end_on(start, end);
189 self
190 }
191}
192
193impl From<VItem> for ranim_core::core_item::vitem::VItem {
194 fn from(value: VItem) -> Self {
195 Self {
196 normal: value.normal.map(|n| n.as_vec3()),
197 points: value.get_render_points(),
198 transform: Mat4::IDENTITY,
199 fill_rgbas: value.fill_rgbas.iter().cloned().collect(),
200 stroke_rgbas: value.stroke_rgbas.iter().cloned().collect(),
201 stroke_widths: value.stroke_widths.iter().cloned().collect(),
202 }
203 }
204}
205
206impl Extract for VItem {
207 type Target = CoreItem;
208 fn extract_into(&self, buf: &mut Vec<Self::Target>) {
209 ranim_core::core_item::vitem::VItem::from(self.clone()).extract_into(buf);
210 }
211}
212
213impl Alignable for VItem {
215 fn is_aligned(&self, other: &Self) -> bool {
216 self.vpoints.is_aligned(&other.vpoints)
217 && self.stroke_widths.is_aligned(&other.stroke_widths)
218 && self.stroke_rgbas.is_aligned(&other.stroke_rgbas)
219 && self.fill_rgbas.is_aligned(&other.fill_rgbas)
220 }
221 fn align_with(&mut self, other: &mut Self) {
222 self.vpoints.align_with(&mut other.vpoints);
223 let len = self.vpoints.len().div_ceil(2);
224 self.stroke_rgbas.resize_preserving_order(len);
225 other.stroke_rgbas.resize_preserving_order(len);
226 self.stroke_widths.resize_preserving_order(len);
227 other.stroke_widths.resize_preserving_order(len);
228 self.fill_rgbas.resize_preserving_order(len);
229 other.fill_rgbas.resize_preserving_order(len);
230 }
231}
232
233impl Opacity for VItem {
234 fn set_opacity(&mut self, opacity: f32) -> &mut Self {
235 self.stroke_rgbas.set_opacity(opacity);
236 self.fill_rgbas.set_opacity(opacity);
237 self
238 }
239}
240
241impl Partial for VItem {
242 fn get_partial(&self, range: std::ops::Range<f64>) -> Self {
243 let vpoints = self.vpoints.get_partial(range.clone());
244 let stroke_rgbas = self.stroke_rgbas.get_partial(range.clone());
245 let stroke_widths = self.stroke_widths.get_partial(range.clone());
246 let fill_rgbas = self.fill_rgbas.get_partial(range.clone());
247 Self {
248 normal: self.normal,
249 vpoints,
250 stroke_widths,
251 stroke_rgbas,
252 fill_rgbas,
253 }
254 }
255 fn get_partial_closed(&self, range: std::ops::Range<f64>) -> Self {
256 let mut partial = self.get_partial(range);
257 partial.close();
258 partial
259 }
260}
261
262impl Empty for VItem {
263 fn empty() -> Self {
264 Self {
265 normal: None,
266 vpoints: VPointVec(vec![DVec3::ZERO; 3]),
267 stroke_widths: vec![0.0.into(); 2].into(),
268 stroke_rgbas: vec![Vec4::ZERO.into(); 2].into(),
269 fill_rgbas: vec![Vec4::ZERO.into(); 2].into(),
270 }
271 }
272}
273
274impl FillColor for VItem {
275 fn fill_color(&self) -> AlphaColor<Srgb> {
276 self.fill_rgbas
277 .first()
278 .map(|&rgba| rgba.into())
279 .unwrap_or(css::WHITE)
280 }
281 fn set_fill_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
282 self.fill_rgbas
283 .iter_mut()
284 .for_each(|rgba| *rgba = color.into());
285 self
286 }
287 fn set_fill_opacity(&mut self, opacity: f32) -> &mut Self {
288 self.fill_rgbas.set_opacity(opacity);
289 self
290 }
291}
292
293impl StrokeColor for VItem {
294 fn stroke_color(&self) -> AlphaColor<Srgb> {
295 self.stroke_rgbas
296 .first()
297 .map(|&rgba| rgba.into())
298 .unwrap_or(css::WHITE)
299 }
300 fn set_stroke_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
301 self.stroke_rgbas
302 .iter_mut()
303 .for_each(|rgba| *rgba = color.into());
304 self
305 }
306 fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self {
307 self.stroke_rgbas.set_opacity(opacity);
308 self
309 }
310}
311
312impl StrokeWidth for VItem {
313 fn stroke_width(&self) -> f32 {
314 self.stroke_widths[0].0
315 }
316 fn apply_stroke_func(&mut self, f: impl for<'a> Fn(&'a mut [Width])) -> &mut Self {
317 f(self.stroke_widths.as_mut());
318 self
319 }
320}
321
322#[cfg(test)]
323mod tests {
324 use ranim_core::{
325 core_item::vitem::vitem_normal_from_points,
326 traits::{Empty, Interpolatable, RotateTransform},
327 };
328
329 use super::{VItem, geometry::Square};
330
331 #[test]
332 fn generated_vitems_derive_their_interpolated_plane() {
333 let source = VItem::from(Square::new(4.0));
334 assert!(source.normal.is_none());
335 assert!(VItem::empty().normal.is_none());
336
337 let mut target = source.clone();
338 target.rotate_on_y(std::f64::consts::PI / 6.0);
339 target.rotate_on_x(std::f64::consts::PI / 6.0);
340
341 let interpolated = source.lerp(&target, 0.4);
342 assert!(interpolated.normal.is_none());
343
344 let core_item = ranim_core::core_item::vitem::VItem::from(interpolated);
345 let normal = vitem_normal_from_points(&core_item.points);
346 let origin = core_item.points[0].truncate();
347 for point in &core_item.points {
348 let distance = (point.truncate() - origin).dot(normal).abs();
349 assert!(distance < 1e-5, "point is {distance} away from its plane");
350 }
351 }
352}