1use std::ops::Range;
4
5use glam::{DAffine3, DVec3, dvec3};
6
7use crate::{
8 anchor::{Aabb, Locate},
9 color::{AlphaColor, Srgb},
10 components::width::Width,
11 traits::{
12 Alignable, ApplyTransform, Diag, Empty, FillColor, Interpolatable, Opacity, Partial, Rigid,
13 Similarity, StrokeColor, StrokeWidth, TransformGroup, Translation,
14 },
15};
16
17#[derive(Debug, Clone, PartialEq)]
42pub struct Transformed<T, G> {
43 pub inner: T,
45 pub transform: G,
47}
48
49impl<T, G> Transformed<T, G> {
50 pub fn new(inner: T, transform: G) -> Self {
52 Self { inner, transform }
53 }
54
55 pub fn map_inner<U, F>(self, f: F) -> Transformed<U, G>
57 where
58 F: FnOnce(T) -> U,
59 {
60 Transformed::new(f(self.inner), self.transform)
61 }
62
63 pub fn map_transform<H, F>(self, f: F) -> Transformed<T, H>
73 where
74 F: FnOnce(G) -> H,
75 {
76 Transformed::new(self.inner, f(self.transform))
77 }
78
79 pub fn compose_outer<H>(&mut self, outer: H) -> &mut Self
81 where
82 G: TransformGroup + From<H>,
83 {
84 self.transform = G::from(outer).compose(&self.transform);
85 self
86 }
87
88 pub fn compose_inner<H>(&mut self, inner: H) -> &mut Self
90 where
91 G: TransformGroup + From<H>,
92 {
93 self.transform = self.transform.compose(&G::from(inner));
94 self
95 }
96
97 pub fn bake(self) -> T
102 where
103 T: ApplyTransform<G>,
104 {
105 let mut inner = self.inner;
106 inner.apply(self.transform);
107 inner
108 }
109}
110
111impl<T, G, H> ApplyTransform<H> for Transformed<T, G>
112where
113 G: TransformGroup + From<H>,
114{
115 fn apply(&mut self, transform: H) -> &mut Self {
116 self.compose_outer(transform)
117 }
118}
119
120impl<T: Interpolatable, G: Interpolatable> Interpolatable for Transformed<T, G> {
121 fn lerp(&self, target: &Self, t: f64) -> Self {
122 Self {
123 inner: self.inner.lerp(&target.inner, t),
124 transform: self.transform.lerp(&target.transform, t),
125 }
126 }
127}
128
129impl<T: Alignable, G: Clone> Alignable for Transformed<T, G> {
130 fn is_aligned(&self, other: &Self) -> bool {
131 self.inner.is_aligned(&other.inner)
132 }
133
134 fn align_with(&mut self, other: &mut Self) {
135 self.inner.align_with(&mut other.inner);
136 }
137}
138
139impl<T, G> Locate<Transformed<T, G>> for crate::anchor::Centroid
140where
141 crate::anchor::Centroid: Locate<T>,
142 G: Clone + Into<DAffine3>,
143{
144 fn locate(&self, target: &Transformed<T, G>) -> DVec3 {
145 target
146 .transform
147 .clone()
148 .into()
149 .transform_point3(self.locate(&target.inner))
150 }
151}
152
153impl<T, G> Aabb for Transformed<T, G>
154where
155 T: Aabb,
156 G: Clone + Into<DAffine3>,
157{
158 fn aabb(&self) -> [DVec3; 2] {
159 let [min, max] = self.inner.aabb();
160 let transform = self.transform.clone().into();
161 let mut lo = DVec3::splat(f64::INFINITY);
162 let mut hi = DVec3::splat(f64::NEG_INFINITY);
163 for i in 0..8 {
164 let corner = dvec3(
165 if i & 1 == 0 { min.x } else { max.x },
166 if i & 2 == 0 { min.y } else { max.y },
167 if i & 4 == 0 { min.z } else { max.z },
168 );
169 let point = transform.transform_point3(corner);
170 lo = lo.min(point);
171 hi = hi.max(point);
172 }
173 [lo, hi]
174 }
175}
176
177impl<T: Opacity, G> Opacity for Transformed<T, G> {
178 fn set_opacity(&mut self, opacity: f32) -> &mut Self {
179 self.inner.set_opacity(opacity);
180 self
181 }
182}
183
184impl<T: FillColor, G> FillColor for Transformed<T, G> {
185 fn fill_color(&self) -> AlphaColor<Srgb> {
186 self.inner.fill_color()
187 }
188
189 fn set_fill_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
190 self.inner.set_fill_color(color);
191 self
192 }
193
194 fn set_fill_opacity(&mut self, opacity: f32) -> &mut Self {
195 self.inner.set_fill_opacity(opacity);
196 self
197 }
198}
199
200impl<T: StrokeColor, G> StrokeColor for Transformed<T, G> {
201 fn stroke_color(&self) -> AlphaColor<Srgb> {
202 self.inner.stroke_color()
203 }
204
205 fn set_stroke_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
206 self.inner.set_stroke_color(color);
207 self
208 }
209
210 fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self {
211 self.inner.set_stroke_opacity(opacity);
212 self
213 }
214}
215
216impl<T: StrokeWidth, G> StrokeWidth for Transformed<T, G> {
217 fn stroke_width(&self) -> f32 {
218 self.inner.stroke_width()
219 }
220
221 fn apply_stroke_func(&mut self, f: impl for<'a> Fn(&'a mut [Width])) -> &mut Self {
222 self.inner.apply_stroke_func(f);
223 self
224 }
225}
226
227impl<T: Partial, G: Clone> Partial for Transformed<T, G> {
228 fn get_partial(&self, range: Range<f64>) -> Self {
236 Transformed::new(self.inner.get_partial(range), self.transform.clone())
237 }
238
239 fn get_partial_closed(&self, range: Range<f64>) -> Self {
245 Transformed::new(self.inner.get_partial_closed(range), self.transform.clone())
246 }
247}
248
249impl<T: Empty, G: TransformGroup> Empty for Transformed<T, G> {
250 fn empty() -> Self {
257 Transformed::new(T::empty(), G::identity())
258 }
259}
260
261macro_rules! impl_transformed_widening {
262 ($source:ty => $target:ty) => {
263 impl<T> From<Transformed<T, $source>> for Transformed<T, $target> {
264 fn from(value: Transformed<T, $source>) -> Self {
265 Self {
266 inner: value.inner,
267 transform: value.transform.into(),
268 }
269 }
270 }
271 };
272}
273
274impl_transformed_widening!(Translation => Rigid);
275impl_transformed_widening!(Translation => Similarity);
276impl_transformed_widening!(Translation => DAffine3);
277impl_transformed_widening!(Rigid => Similarity);
278impl_transformed_widening!(Rigid => DAffine3);
279impl_transformed_widening!(Similarity => DAffine3);
280impl_transformed_widening!(Diag => DAffine3);
281
282pub trait TransformedExt: Sized {
284 fn transformed<G>(self, transform: G) -> Transformed<Self, G> {
286 Transformed::new(self, transform)
287 }
288}
289
290impl<T> TransformedExt for T {}
291
292#[cfg(test)]
293mod tests {
294 use super::*;
295 use crate::{
296 RanimScene,
297 animation::{build::PlaybackExt, eval::Static},
298 frame::EvaluatedFrame,
299 traits::{ShiftTransform, UniformScaleTransform},
300 };
301 use glam::DQuat;
302
303 #[derive(Debug, Clone, PartialEq)]
304 struct LocalGeometry {
305 points: Vec<DVec3>,
306 transform: DAffine3,
307 }
308
309 fn assert_affine_eq(actual: DAffine3, expected: DAffine3) {
310 assert!(actual.matrix3.abs_diff_eq(expected.matrix3, 1e-9));
311 assert!(actual.translation.abs_diff_eq(expected.translation, 1e-9));
312 }
313
314 #[test]
315 fn evaluation_preserves_the_typed_wrapper_and_local_geometry() {
316 let geometry = LocalGeometry {
317 points: vec![DVec3::ZERO; 3],
318 transform: DAffine3::from_translation(DVec3::X),
319 };
320 let wrapped = geometry.transformed(Translation(dvec3(0.0, 2.0, 0.0)));
321 let mut scene = RanimScene::new();
322 scene.play(Static(wrapped.clone()).with_duration(1.0));
323 let mut frame = EvaluatedFrame::new();
324 scene.seal().eval_at_sec(0.5, &mut frame);
325
326 let items = frame.items::<Transformed<LocalGeometry, Translation>>();
327 assert_eq!(items.len(), 1);
328 assert_eq!(items[0].value, wrapped);
329 assert_eq!(
330 items[0].value.inner.transform,
331 DAffine3::from_translation(DVec3::X)
332 );
333 assert_eq!(items[0].value.inner.points, vec![DVec3::ZERO; 3]);
334 assert!(frame.items::<LocalGeometry>().is_empty());
335 }
336
337 #[test]
338 fn transforms_compose_inside_out() {
339 let inner = vec![DVec3::X].transformed(Translation(DVec3::X));
340 let outer = inner.transformed(Diag(DVec3::splat(2.0)));
341 assert_eq!(outer.inner.inner, vec![DVec3::X]);
342 assert_affine_eq(
343 DAffine3::from(outer.transform) * DAffine3::from(outer.inner.transform),
344 DAffine3::from_scale(DVec3::splat(2.0)) * DAffine3::from_translation(DVec3::X),
345 );
346
347 let mut wrapped = ().transformed(DAffine3::IDENTITY);
348 wrapped.compose_outer(Translation(DVec3::X));
349 wrapped.compose_inner(Diag(DVec3::splat(2.0)));
350 assert_affine_eq(
351 wrapped.transform,
352 DAffine3::from_translation(DVec3::X) * DAffine3::from_scale(DVec3::splat(2.0)),
353 );
354
355 wrapped.apply(Translation(DVec3::Y));
356 assert_affine_eq(
357 wrapped.transform,
358 DAffine3::from_translation(DVec3::Y)
359 * DAffine3::from_translation(DVec3::X)
360 * DAffine3::from_scale(DVec3::splat(2.0)),
361 );
362 }
363
364 #[test]
365 fn interpolation_lerps_inner_and_pose_by_storage_group() {
366 let a = 0.0f64.transformed(Similarity::IDENTITY);
367 let b = 2.0f64.transformed(Similarity {
368 scale: 3.0,
369 rotation: DQuat::IDENTITY,
370 translation: dvec3(2.0, 0.0, 0.0),
371 });
372 let mid = a.lerp(&b, 0.5);
373 assert_eq!(mid.inner, 1.0);
374 assert_eq!(mid.transform.scale, 2.0);
375 assert_eq!(mid.transform.translation, DVec3::X);
376
377 let points = vec![DVec3::X];
378 let a = points.clone().transformed(Translation(DVec3::X));
379 let b = points.transformed(Translation(dvec3(-1.0, 2.0, 0.0)));
380 let mid = a.lerp(&b, 0.5);
381 assert_eq!(mid.inner, vec![DVec3::X]);
383 assert_affine_eq(
384 DAffine3::from(mid.transform),
385 DAffine3::from_translation(dvec3(0.0, 1.0, 0.0)),
386 );
387
388 let a = 42.0f64.transformed(Rigid::IDENTITY);
389 let b = 42.0f64.transformed(Rigid::from_axis_angle(
390 DVec3::Z,
391 std::f64::consts::FRAC_PI_2,
392 ));
393 let mid = a.lerp(&b, 0.5);
394 let rotated = mid.transform.rotation * DVec3::X;
395 let expected = DQuat::from_axis_angle(DVec3::Z, std::f64::consts::FRAC_PI_4) * DVec3::X;
396 assert!(rotated.abs_diff_eq(expected, 1e-9));
397 }
398
399 #[test]
400 fn anchor_queries_apply_the_outer_pose() {
401 let wrapped = dvec3(1.0, 1.0, 1.0).transformed(Translation(dvec3(10.0, 0.0, 0.0)));
402 assert_eq!(wrapped.aabb(), [dvec3(11.0, 1.0, 1.0); 2]);
403
404 let wrapped = dvec3(1.0, 2.0, 3.0).transformed(Translation(dvec3(4.0, 5.0, 6.0)));
405 assert_eq!(
406 crate::anchor::Centroid.locate(&wrapped),
407 dvec3(5.0, 7.0, 9.0)
408 );
409 }
410
411 #[test]
412 fn map_operations_preserve_pose_and_support_storage_conversion() {
413 let mapped = vec![DVec3::ZERO; 3]
414 .transformed(Translation(DVec3::X))
415 .map_inner(|points| points.len())
416 .map_transform(DAffine3::from);
417 assert_eq!(mapped.inner, 3);
418 assert_eq!(mapped.transform, DAffine3::from_translation(DVec3::X));
419 }
420
421 #[test]
422 fn subgroup_operations_keep_the_same_storage_type() {
423 fn require_similarity<T>(_: &Transformed<T, Similarity>) {}
424
425 let mut wrapped = ().transformed(Similarity::IDENTITY);
426 wrapped.shift(DVec3::X).scale_uniform(2.0);
427 require_similarity(&wrapped);
428 assert_eq!(wrapped.transform.scale, 2.0);
429 assert_eq!(wrapped.transform.translation, dvec3(2.0, 0.0, 0.0));
430 }
431
432 #[test]
433 fn explicit_widening_preserves_numeric_transform() {
434 let translation = ().transformed(Translation(dvec3(1.0, 2.0, 3.0)));
435 let rigid: Transformed<_, Rigid> = translation.clone().into();
436 let similarity: Transformed<_, Similarity> = translation.clone().into();
437 let affine: Transformed<_, DAffine3> = translation.into();
438 assert_affine_eq(rigid.transform.into(), affine.transform);
439 assert_affine_eq(similarity.transform.into(), affine.transform);
440
441 let rigid = ().transformed(Rigid {
442 rotation: DQuat::from_rotation_z(0.7),
443 translation: DVec3::Y,
444 });
445 let similarity: Transformed<_, Similarity> = rigid.clone().into();
446 let affine: Transformed<_, DAffine3> = rigid.into();
447 assert_affine_eq(similarity.transform.into(), affine.transform);
448
449 let similarity = ().transformed(Similarity {
450 scale: 2.5,
451 rotation: DQuat::from_rotation_x(0.4),
452 translation: DVec3::Z,
453 });
454 let affine: Transformed<_, DAffine3> = similarity.into();
455 assert_affine_eq(
456 affine.transform,
457 DAffine3::from_scale_rotation_translation(
458 DVec3::splat(2.5),
459 DQuat::from_rotation_x(0.4),
460 DVec3::Z,
461 ),
462 );
463
464 let diagonal = ().transformed(Diag(dvec3(2.0, 0.0, -3.0)));
465 let affine_diagonal: Transformed<_, DAffine3> = diagonal.into();
466 assert_eq!(affine_diagonal.transform.matrix3.y_axis, DVec3::ZERO);
467 assert_eq!(affine_diagonal.transform.matrix3.z_axis.z, -3.0);
468 }
469
470 #[derive(Clone, Debug, PartialEq)]
474 struct StubShape {
475 range: Range<f64>,
477 closed: bool,
478 }
479
480 impl Partial for StubShape {
481 fn get_partial(&self, range: Range<f64>) -> Self {
482 Self {
483 range,
484 closed: false,
485 }
486 }
487
488 fn get_partial_closed(&self, range: Range<f64>) -> Self {
489 Self {
490 range,
491 closed: true,
492 }
493 }
494 }
495
496 impl Empty for StubShape {
497 fn empty() -> Self {
498 Self {
499 range: 0.0..0.0,
500 closed: false,
501 }
502 }
503 }
504
505 #[test]
506 fn partial_forwards_the_range_and_keeps_the_pose() {
507 let wrapped = StubShape {
508 range: 0.0..1.0,
509 closed: false,
510 }
511 .transformed(Translation(DVec3::X));
512
513 let partial = wrapped.get_partial(0.2..0.8);
514 assert_eq!(partial.inner.range, 0.2..0.8);
516 assert!(!partial.inner.closed);
517 assert_eq!(partial.transform, wrapped.transform);
519
520 let closed = wrapped.get_partial_closed(0.3..0.6);
521 assert_eq!(closed.inner.range, 0.3..0.6);
522 assert!(closed.inner.closed);
523 assert_eq!(closed.transform, wrapped.transform);
524 }
525
526 #[test]
527 fn empty_wrapper_carries_an_identity_pose() {
528 let wrapped: Transformed<StubShape, Translation> = Empty::empty();
529 assert_eq!(wrapped.inner, StubShape::empty());
530 assert_eq!(wrapped.transform, Translation(DVec3::ZERO));
531 }
532}