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, 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#[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 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
125pub use ranim_core::core_item::vitem::DEFAULT_STROKE_WIDTH;
134
135impl VItem {
136 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 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 pub fn set_points(&mut self, vpoints: Vec<DVec3>) {
153 self.vpoints.0 = vpoints;
154 }
155 pub fn get_anchor(&self, idx: usize) -> Option<&DVec3> {
157 self.vpoints.get(idx * 2)
158 }
159 pub fn with_normal(mut self, normal: DVec3) -> Self {
161 self.normal = Some(normal);
162 self
163 }
164 pub fn set_normal(&mut self, normal: DVec3) {
166 self.normal = Some(normal);
167 }
168 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 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 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
224impl 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}