1#[cfg(feature = "render")]
11use ranim_render::extract::{Extract, ExtractContext};
12
13pub mod geometry;
15#[cfg(feature = "render")]
17pub mod render;
18pub mod svg;
20#[cfg(feature = "typst")]
22#[cfg_attr(docsrs, doc(cfg(feature = "typst")))]
23pub mod text;
24#[cfg(feature = "typst")]
26#[cfg_attr(docsrs, doc(cfg(feature = "typst")))]
27pub mod typst;
28
29#[cfg(feature = "render")]
35pub struct VItemPlugin;
36
37#[cfg(feature = "render")]
38impl bevy_app::Plugin for VItemPlugin {
39 fn build(&self, app: &mut bevy_app::App) {
40 render::install(app);
41 }
42}
43
44use color::{AlphaColor, Srgb, palette::css};
45use glam::{DVec3, Vec4, vec4};
46use ranim_core::anchor::Aabb;
47use ranim_core::{color, glam};
48
49use ranim_core::{
50 components::{PointVec, VecResizeTrait, rgba::Rgba, vpoint::VPointVec, width::Width},
51 prelude::{Alignable, Empty, FillColor, Opacity, Partial, StrokeWidth},
52 traits::{ApplyTransform, PointsFunc, StrokeColor},
53};
54
55#[derive(Debug, Clone, PartialEq)]
76pub struct VItem {
77 pub normal: Option<DVec3>,
80 pub vpoints: VPointVec,
82 pub stroke_widths: PointVec<Width>,
84 pub stroke_rgbas: PointVec<Rgba>,
86 pub fill_rgbas: PointVec<Rgba>,
88}
89
90impl ranim_core::traits::Interpolatable for VItem {
91 fn lerp(&self, target: &Self, t: f64) -> Self {
92 Self {
93 normal: match (self.normal, target.normal) {
94 (Some(a), Some(b)) => Some(a.lerp(b, t)),
95 (Some(a), None) => Some(a),
96 (None, Some(b)) => Some(b),
97 (None, None) => None,
98 },
99 vpoints: self.vpoints.lerp(&target.vpoints, t),
100 stroke_widths: self.stroke_widths.lerp(&target.stroke_widths, t),
101 stroke_rgbas: self.stroke_rgbas.lerp(&target.stroke_rgbas, t),
102 fill_rgbas: self.fill_rgbas.lerp(&target.fill_rgbas, t),
103 }
104 }
105}
106
107impl PointsFunc for VItem {
108 fn apply_points_func(&mut self, f: impl Fn(&mut [DVec3])) -> &mut Self {
109 self.vpoints.apply_points_func(f);
110 self
111 }
112}
113
114impl Aabb for VItem {
115 fn aabb(&self) -> [DVec3; 2] {
116 self.vpoints.aabb()
117 }
118}
119
120impl<G: Into<glam::DAffine3>> ApplyTransform<G> for VItem {
121 fn apply(&mut self, transform: G) -> &mut Self {
122 let transform = transform.into();
123 self.vpoints.apply(transform);
124 if let Some(ref mut normal) = self.normal {
125 let matrix = transform.matrix3;
126 let transformed = if matrix.determinant().abs() > 1e-12 {
127 matrix.inverse().transpose() * *normal
128 } else {
129 matrix * *normal
130 };
131 if let Some(unit) = transformed.try_normalize() {
132 *normal = unit;
133 }
134 }
135 self
136 }
137}
138
139pub const DEFAULT_STROKE_WIDTH: f32 = 0.02;
141
142impl VItem {
143 pub fn close(&mut self) -> &mut Self {
145 if self.vpoints.last() != self.vpoints.first() && !self.vpoints.is_empty() {
146 let start = self.vpoints[0];
147 let end = self.vpoints[self.vpoints.len() - 1];
148 self.extend_vpoints(&[(start + end) / 2.0, start]);
149 }
150 self
151 }
152 pub fn shrink(&mut self) -> &mut Self {
154 let bb = self.aabb();
155 self.vpoints.0 = vec![bb[1]; self.vpoints.len()];
156 self
157 }
158 pub fn set_points(&mut self, vpoints: Vec<DVec3>) {
160 self.vpoints.0 = vpoints;
161 }
162 pub fn get_anchor(&self, idx: usize) -> Option<&DVec3> {
164 self.vpoints.get(idx * 2)
165 }
166 pub fn with_normal(mut self, normal: DVec3) -> Self {
168 self.normal = Some(normal);
169 self
170 }
171 pub fn set_normal(&mut self, normal: DVec3) {
173 self.normal = Some(normal);
174 }
175 pub fn from_vpoints(vpoints: Vec<DVec3>) -> Self {
177 let stroke_widths = vec![DEFAULT_STROKE_WIDTH.into(); vpoints.len().div_ceil(2)];
178 let stroke_rgbas = vec![vec4(1.0, 1.0, 1.0, 1.0).into(); vpoints.len().div_ceil(2)];
179 let fill_rgbas = vec![vec4(0.0, 0.0, 0.0, 0.0).into(); vpoints.len().div_ceil(2)];
180 Self {
181 normal: None,
182 vpoints: VPointVec(vpoints),
183 stroke_rgbas: stroke_rgbas.into(),
184 stroke_widths: stroke_widths.into(),
185 fill_rgbas: fill_rgbas.into(),
186 }
187 }
188 pub fn extend_vpoints(&mut self, vpoints: &[DVec3]) {
190 self.vpoints.extend(vpoints.to_vec());
191
192 let len = self.vpoints.len();
193 self.fill_rgbas.resize_with_last(len.div_ceil(2));
194 self.stroke_rgbas.resize_with_last(len.div_ceil(2));
195 self.stroke_widths.resize_with_last(len.div_ceil(2));
196 }
197
198 #[cfg(feature = "render")]
199 pub(crate) fn get_render_points(&self) -> Vec<Vec4> {
200 self.vpoints
201 .iter()
202 .zip(self.vpoints.get_closepath_flags())
203 .map(|(p, f)| p.as_vec3().extend(f.into()))
204 .collect()
205 }
206 pub fn put_start_and_end_on(&mut self, start: DVec3, end: DVec3) -> &mut Self {
208 self.vpoints.put_start_and_end_on(start, end);
209 self
210 }
211}
212
213#[cfg(feature = "render")]
214impl From<VItem> for render::RenderVItem {
215 fn from(value: VItem) -> Self {
216 Self {
217 normal: value.normal.map(|n| n.as_vec3()),
218 points: value.get_render_points(),
219 transform: glam::Mat4::IDENTITY,
220 fill_rgbas: value.fill_rgbas.iter().cloned().collect(),
221 stroke_rgbas: value.stroke_rgbas.iter().cloned().collect(),
222 stroke_widths: value.stroke_widths.iter().cloned().collect(),
223 }
224 }
225}
226
227#[cfg(feature = "render")]
228impl Extract for VItem {
229 fn extract(&self, ctx: &mut ExtractContext<'_>) {
230 let mut value = render::RenderVItem::from(self.clone());
231 value.transform = glam::DMat4::from(ctx.transform()).as_mat4() * value.transform;
232 let mut entity = ctx.entity::<render::RenderVItem>();
233 if entity.get::<render::RenderVItem>() != Some(&value) {
234 entity.insert(value);
235 }
236 }
237}
238
239impl Alignable for VItem {
241 fn is_aligned(&self, other: &Self) -> bool {
242 self.vpoints.is_aligned(&other.vpoints)
243 && self.stroke_widths.is_aligned(&other.stroke_widths)
244 && self.stroke_rgbas.is_aligned(&other.stroke_rgbas)
245 && self.fill_rgbas.is_aligned(&other.fill_rgbas)
246 }
247 fn align_with(&mut self, other: &mut Self) {
248 self.vpoints.align_with(&mut other.vpoints);
249 let len = self.vpoints.len().div_ceil(2);
250 self.stroke_rgbas.resize_preserving_order(len);
251 other.stroke_rgbas.resize_preserving_order(len);
252 self.stroke_widths.resize_preserving_order(len);
253 other.stroke_widths.resize_preserving_order(len);
254 self.fill_rgbas.resize_preserving_order(len);
255 other.fill_rgbas.resize_preserving_order(len);
256 }
257}
258
259impl Opacity for VItem {
260 fn set_opacity(&mut self, opacity: f32) -> &mut Self {
261 self.stroke_rgbas.set_opacity(opacity);
262 self.fill_rgbas.set_opacity(opacity);
263 self
264 }
265}
266
267impl Partial for VItem {
268 fn get_partial(&self, range: std::ops::Range<f64>) -> Self {
269 let vpoints = self.vpoints.get_partial(range.clone());
270 let stroke_rgbas = self.stroke_rgbas.get_partial(range.clone());
271 let stroke_widths = self.stroke_widths.get_partial(range.clone());
272 let fill_rgbas = self.fill_rgbas.get_partial(range.clone());
273 Self {
274 normal: self.normal,
275 vpoints,
276 stroke_widths,
277 stroke_rgbas,
278 fill_rgbas,
279 }
280 }
281 fn get_partial_closed(&self, range: std::ops::Range<f64>) -> Self {
282 let mut partial = self.get_partial(range);
283 partial.close();
284 partial
285 }
286}
287
288impl Empty for VItem {
289 fn empty() -> Self {
290 Self {
291 normal: None,
292 vpoints: VPointVec(vec![DVec3::ZERO; 3]),
293 stroke_widths: vec![0.0.into(); 2].into(),
294 stroke_rgbas: vec![Vec4::ZERO.into(); 2].into(),
295 fill_rgbas: vec![Vec4::ZERO.into(); 2].into(),
296 }
297 }
298}
299
300impl FillColor for VItem {
301 fn fill_color(&self) -> AlphaColor<Srgb> {
302 self.fill_rgbas
303 .first()
304 .map(|&rgba| rgba.into())
305 .unwrap_or(css::WHITE)
306 }
307 fn set_fill_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
308 self.fill_rgbas
309 .iter_mut()
310 .for_each(|rgba| *rgba = color.into());
311 self
312 }
313 fn set_fill_opacity(&mut self, opacity: f32) -> &mut Self {
314 self.fill_rgbas.set_opacity(opacity);
315 self
316 }
317}
318
319impl StrokeColor for VItem {
320 fn stroke_color(&self) -> AlphaColor<Srgb> {
321 self.stroke_rgbas
322 .first()
323 .map(|&rgba| rgba.into())
324 .unwrap_or(css::WHITE)
325 }
326 fn set_stroke_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
327 self.stroke_rgbas
328 .iter_mut()
329 .for_each(|rgba| *rgba = color.into());
330 self
331 }
332 fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self {
333 self.stroke_rgbas.set_opacity(opacity);
334 self
335 }
336}
337
338impl StrokeWidth for VItem {
339 fn stroke_width(&self) -> f32 {
340 self.stroke_widths[0].0
341 }
342 fn apply_stroke_func(&mut self, f: impl for<'a> Fn(&'a mut [Width])) -> &mut Self {
343 f(self.stroke_widths.as_mut());
344 self
345 }
346}
347
348#[cfg(test)]
349mod tests {
350 use ranim_core::traits::{Empty, Interpolatable, RotateTransform};
351
352 use super::{VItem, geometry::Square};
353
354 #[test]
355 fn generated_vitems_derive_their_interpolated_plane() {
356 let source = VItem::from(Square::new(4.0));
357 assert!(source.normal.is_none());
358 assert!(VItem::empty().normal.is_none());
359
360 let mut target = source.clone();
361 target.rotate_on_y(std::f64::consts::PI / 6.0);
362 target.rotate_on_x(std::f64::consts::PI / 6.0);
363
364 let interpolated = source.lerp(&target, 0.4);
365 assert!(interpolated.normal.is_none());
366
367 let points = &interpolated.vpoints;
368 let origin = points[0];
369 let normal = (points[1] - origin).cross(points[3] - origin).normalize();
370 for point in points.iter() {
371 let distance = (*point - origin).dot(normal).abs();
372 assert!(distance < 1e-5, "point is {distance} away from its plane");
373 }
374 }
375}