1use std::ops::Range;
4
5use glam::{DAffine3, DVec3, dvec3};
6
7use crate::{
8 Extract,
9 anchor::{Aabb, Locate},
10 color::{AlphaColor, Srgb},
11 components::width::Width,
12 core_item::CoreItem,
13 traits::{
14 Alignable, ApplyTransform, Diag, Empty, FillColor, Interpolatable, Opacity, Partial, Rigid,
15 Similarity, StrokeColor, StrokeWidth, TransformGroup, Translation,
16 },
17};
18
19#[derive(Debug, Clone, PartialEq)]
44pub struct Transformed<T, G> {
45 pub inner: T,
47 pub transform: G,
49}
50
51impl<T, G> Transformed<T, G> {
52 pub fn new(inner: T, transform: G) -> Self {
54 Self { inner, transform }
55 }
56
57 pub fn map_inner<U, F>(self, f: F) -> Transformed<U, G>
59 where
60 F: FnOnce(T) -> U,
61 {
62 Transformed::new(f(self.inner), self.transform)
63 }
64
65 pub fn map_transform<H, F>(self, f: F) -> Transformed<T, H>
75 where
76 F: FnOnce(G) -> H,
77 {
78 Transformed::new(self.inner, f(self.transform))
79 }
80
81 pub fn compose_outer<H>(&mut self, outer: H) -> &mut Self
83 where
84 G: TransformGroup + From<H>,
85 {
86 self.transform = G::from(outer).compose(&self.transform);
87 self
88 }
89
90 pub fn compose_inner<H>(&mut self, inner: H) -> &mut Self
92 where
93 G: TransformGroup + From<H>,
94 {
95 self.transform = self.transform.compose(&G::from(inner));
96 self
97 }
98
99 pub fn bake(self) -> T
104 where
105 T: ApplyTransform<G>,
106 {
107 let mut inner = self.inner;
108 inner.apply(self.transform);
109 inner
110 }
111}
112
113impl<T, G, H> ApplyTransform<H> for Transformed<T, G>
114where
115 G: TransformGroup + From<H>,
116{
117 fn apply(&mut self, transform: H) -> &mut Self {
118 self.compose_outer(transform)
119 }
120}
121
122impl<T, G> Extract for Transformed<T, G>
123where
124 T: Extract<Target = CoreItem>,
125 G: Clone + Into<DAffine3>,
126{
127 type Target = CoreItem;
128
129 fn extract_into(&self, buf: &mut Vec<Self::Target>) {
130 let start = buf.len();
131 self.inner.extract_into(buf);
132 let transform = self.transform.clone().into();
133 for item in &mut buf[start..] {
134 item.apply_transform(&transform);
135 }
136 }
137}
138
139impl<T: Interpolatable, G: Interpolatable> Interpolatable for Transformed<T, G> {
140 fn lerp(&self, target: &Self, t: f64) -> Self {
141 Self {
142 inner: self.inner.lerp(&target.inner, t),
143 transform: self.transform.lerp(&target.transform, t),
144 }
145 }
146}
147
148impl<T: Alignable, G: Clone> Alignable for Transformed<T, G> {
149 fn is_aligned(&self, other: &Self) -> bool {
150 self.inner.is_aligned(&other.inner)
151 }
152
153 fn align_with(&mut self, other: &mut Self) {
154 self.inner.align_with(&mut other.inner);
155 }
156}
157
158impl<T, G> Locate<Transformed<T, G>> for crate::anchor::Centroid
159where
160 crate::anchor::Centroid: Locate<T>,
161 G: Clone + Into<DAffine3>,
162{
163 fn locate(&self, target: &Transformed<T, G>) -> DVec3 {
164 target
165 .transform
166 .clone()
167 .into()
168 .transform_point3(self.locate(&target.inner))
169 }
170}
171
172impl<T, G> Aabb for Transformed<T, G>
173where
174 T: Aabb,
175 G: Clone + Into<DAffine3>,
176{
177 fn aabb(&self) -> [DVec3; 2] {
178 let [min, max] = self.inner.aabb();
179 let transform = self.transform.clone().into();
180 let mut lo = DVec3::splat(f64::INFINITY);
181 let mut hi = DVec3::splat(f64::NEG_INFINITY);
182 for i in 0..8 {
183 let corner = dvec3(
184 if i & 1 == 0 { min.x } else { max.x },
185 if i & 2 == 0 { min.y } else { max.y },
186 if i & 4 == 0 { min.z } else { max.z },
187 );
188 let point = transform.transform_point3(corner);
189 lo = lo.min(point);
190 hi = hi.max(point);
191 }
192 [lo, hi]
193 }
194}
195
196impl<T: Opacity, G> Opacity for Transformed<T, G> {
197 fn set_opacity(&mut self, opacity: f32) -> &mut Self {
198 self.inner.set_opacity(opacity);
199 self
200 }
201}
202
203impl<T: FillColor, G> FillColor for Transformed<T, G> {
204 fn fill_color(&self) -> AlphaColor<Srgb> {
205 self.inner.fill_color()
206 }
207
208 fn set_fill_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
209 self.inner.set_fill_color(color);
210 self
211 }
212
213 fn set_fill_opacity(&mut self, opacity: f32) -> &mut Self {
214 self.inner.set_fill_opacity(opacity);
215 self
216 }
217}
218
219impl<T: StrokeColor, G> StrokeColor for Transformed<T, G> {
220 fn stroke_color(&self) -> AlphaColor<Srgb> {
221 self.inner.stroke_color()
222 }
223
224 fn set_stroke_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
225 self.inner.set_stroke_color(color);
226 self
227 }
228
229 fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self {
230 self.inner.set_stroke_opacity(opacity);
231 self
232 }
233}
234
235impl<T: StrokeWidth, G> StrokeWidth for Transformed<T, G> {
236 fn stroke_width(&self) -> f32 {
237 self.inner.stroke_width()
238 }
239
240 fn apply_stroke_func(&mut self, f: impl for<'a> Fn(&'a mut [Width])) -> &mut Self {
241 self.inner.apply_stroke_func(f);
242 self
243 }
244}
245
246impl<T: Partial, G: Clone> Partial for Transformed<T, G> {
247 fn get_partial(&self, range: Range<f64>) -> Self {
255 Transformed::new(self.inner.get_partial(range), self.transform.clone())
256 }
257
258 fn get_partial_closed(&self, range: Range<f64>) -> Self {
264 Transformed::new(self.inner.get_partial_closed(range), self.transform.clone())
265 }
266}
267
268impl<T: Empty, G: TransformGroup> Empty for Transformed<T, G> {
269 fn empty() -> Self {
276 Transformed::new(T::empty(), G::identity())
277 }
278}
279
280macro_rules! impl_transformed_widening {
281 ($source:ty => $target:ty) => {
282 impl<T> From<Transformed<T, $source>> for Transformed<T, $target> {
283 fn from(value: Transformed<T, $source>) -> Self {
284 Self {
285 inner: value.inner,
286 transform: value.transform.into(),
287 }
288 }
289 }
290 };
291}
292
293impl_transformed_widening!(Translation => Rigid);
294impl_transformed_widening!(Translation => Similarity);
295impl_transformed_widening!(Translation => DAffine3);
296impl_transformed_widening!(Rigid => Similarity);
297impl_transformed_widening!(Rigid => DAffine3);
298impl_transformed_widening!(Similarity => DAffine3);
299impl_transformed_widening!(Diag => DAffine3);
300
301pub trait TransformedExt: Sized {
303 fn transformed<G>(self, transform: G) -> Transformed<Self, G> {
305 Transformed::new(self, transform)
306 }
307}
308
309impl<T> TransformedExt for T {}
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314 use crate::{
315 core_item::{mesh_item::MeshItem, vitem::VItem},
316 traits::{ShiftTransform, UniformScaleTransform},
317 };
318 use glam::{DQuat, Vec3, Vec4};
319
320 fn assert_affine_eq(actual: DAffine3, expected: DAffine3) {
321 assert!(
322 actual
323 .transform_point3(dvec3(0.3, -0.7, 1.1))
324 .abs_diff_eq(expected.transform_point3(dvec3(0.3, -0.7, 1.1)), 1e-9)
325 );
326 }
327
328 #[test]
329 fn extract_composes_pose_and_keeps_geometry_local() {
330 let mesh = MeshItem {
331 transform: glam::Mat4::from_translation(Vec3::X),
332 ..Default::default()
333 };
334 let wrapped = mesh.transformed(Translation(dvec3(0.0, 2.0, 0.0)));
335 match &wrapped.extract()[0] {
336 CoreItem::MeshItem(mesh) => {
337 assert_eq!(
338 mesh.transform,
339 glam::Mat4::from_translation(Vec3::new(1.0, 2.0, 0.0))
340 );
341 assert_eq!(mesh.points, vec![Vec3::ZERO; 3]);
342 }
343 _ => panic!("expected MeshItem"),
344 }
345
346 let vitem = VItem {
347 points: vec![Vec4::new(1.0, 0.0, 0.0, 0.0)],
348 normal: Some(Vec3::new(1.0, 1.0, 0.0).normalize()),
349 ..Default::default()
350 };
351 let wrapped = vitem.transformed(DAffine3::from_scale_rotation_translation(
352 dvec3(2.0, 1.0, 1.0),
353 DQuat::IDENTITY,
354 dvec3(0.0, 2.0, 0.0),
355 ));
356 match &wrapped.extract()[0] {
357 CoreItem::VItem(vitem) => {
358 assert_eq!(vitem.points[0], Vec4::new(1.0, 0.0, 0.0, 0.0));
360 assert_eq!(vitem.normal, Some(Vec3::new(1.0, 1.0, 0.0).normalize()));
361 assert_eq!(
362 vitem.transform,
363 glam::Mat4::from_scale_rotation_translation(
364 Vec3::new(2.0, 1.0, 1.0),
365 glam::Quat::IDENTITY,
366 Vec3::new(0.0, 2.0, 0.0),
367 )
368 );
369 }
370 _ => panic!("expected VItem"),
371 }
372 }
373
374 #[test]
375 fn transforms_compose_inside_out() {
376 let inner = VItem {
377 points: vec![Vec4::new(1.0, 0.0, 0.0, 0.0)],
378 ..Default::default()
379 }
380 .transformed(Translation(DVec3::X));
381 let outer = inner.transformed(Diag(DVec3::splat(2.0)));
382 match &outer.extract()[0] {
383 CoreItem::VItem(vitem) => {
384 assert_eq!(vitem.points[0], Vec4::new(1.0, 0.0, 0.0, 0.0));
385 assert_eq!(vitem.transform.w_axis.truncate(), Vec3::new(2.0, 0.0, 0.0));
386 assert_eq!(vitem.transform.x_axis.truncate(), Vec3::new(2.0, 0.0, 0.0));
387 }
388 _ => panic!("expected VItem"),
389 }
390
391 let mut wrapped = ().transformed(DAffine3::IDENTITY);
392 wrapped.compose_outer(Translation(DVec3::X));
393 wrapped.compose_inner(Diag(DVec3::splat(2.0)));
394 assert_affine_eq(
395 wrapped.transform,
396 DAffine3::from_translation(DVec3::X) * DAffine3::from_scale(DVec3::splat(2.0)),
397 );
398
399 wrapped.apply(Translation(DVec3::Y));
400 assert_affine_eq(
401 wrapped.transform,
402 DAffine3::from_translation(DVec3::Y)
403 * DAffine3::from_translation(DVec3::X)
404 * DAffine3::from_scale(DVec3::splat(2.0)),
405 );
406 }
407
408 #[test]
409 fn interpolation_lerps_inner_and_pose_by_storage_group() {
410 let a = 0.0f64.transformed(Similarity::IDENTITY);
411 let b = 2.0f64.transformed(Similarity {
412 scale: 3.0,
413 rotation: DQuat::IDENTITY,
414 translation: dvec3(2.0, 0.0, 0.0),
415 });
416 let mid = a.lerp(&b, 0.5);
417 assert_eq!(mid.inner, 1.0);
418 assert_eq!(mid.transform.scale, 2.0);
419 assert_eq!(mid.transform.translation, DVec3::X);
420
421 let points = vec![Vec4::new(1.0, 0.0, 0.0, 0.0)];
422 let a = VItem {
423 points: points.clone(),
424 ..Default::default()
425 }
426 .transformed(Translation(DVec3::X));
427 let b = VItem {
428 points,
429 ..Default::default()
430 }
431 .transformed(Translation(dvec3(-1.0, 2.0, 0.0)));
432 let mid = a.lerp(&b, 0.5);
433 assert_eq!(mid.inner.points[0], Vec4::new(1.0, 0.0, 0.0, 0.0));
435 assert_affine_eq(
436 DAffine3::from(mid.transform),
437 DAffine3::from_translation(dvec3(0.0, 1.0, 0.0)),
438 );
439
440 let a = 42.0f64.transformed(Rigid::IDENTITY);
441 let b = 42.0f64.transformed(Rigid::from_axis_angle(
442 DVec3::Z,
443 std::f64::consts::FRAC_PI_2,
444 ));
445 let mid = a.lerp(&b, 0.5);
446 let rotated = mid.transform.rotation * DVec3::X;
447 let expected = DQuat::from_axis_angle(DVec3::Z, std::f64::consts::FRAC_PI_4) * DVec3::X;
448 assert!(rotated.abs_diff_eq(expected, 1e-9));
449 }
450
451 #[test]
452 fn anchor_queries_apply_the_outer_pose() {
453 let wrapped = dvec3(1.0, 1.0, 1.0).transformed(Translation(dvec3(10.0, 0.0, 0.0)));
454 assert_eq!(wrapped.aabb(), [dvec3(11.0, 1.0, 1.0); 2]);
455
456 let wrapped = dvec3(1.0, 2.0, 3.0).transformed(Translation(dvec3(4.0, 5.0, 6.0)));
457 assert_eq!(
458 crate::anchor::Centroid.locate(&wrapped),
459 dvec3(5.0, 7.0, 9.0)
460 );
461 }
462
463 #[test]
464 fn map_operations_preserve_pose_and_support_storage_conversion() {
465 let wrapped = VItem::default()
466 .transformed(Translation(DVec3::X))
467 .map_inner(|item: VItem| item.points.len());
468 assert_eq!(wrapped.inner, 3);
469 assert_eq!(wrapped.transform, Translation(DVec3::X));
470
471 fn to_affine(translation: Translation) -> DAffine3 {
472 translation.into()
473 }
474 let by_fn = 42u32
475 .transformed(Translation(DVec3::X))
476 .map_transform(to_affine);
477 assert_eq!(by_fn.inner, 42);
478 assert_affine_eq(by_fn.transform, DAffine3::from_translation(DVec3::X));
479
480 let by_closure = 42u32
481 .transformed(Translation(DVec3::X))
482 .map_transform(Similarity::from);
483 assert_eq!(by_closure.inner, 42);
484 assert_eq!(by_closure.transform.translation, DVec3::X);
485
486 let vitem = VItem {
487 points: vec![Vec4::new(1.0, 0.0, 0.0, 0.0)],
488 ..Default::default()
489 };
490 let extracted = vitem
491 .transformed(Translation(DVec3::X))
492 .map_transform(DAffine3::from);
493 match &extracted.extract()[0] {
494 CoreItem::VItem(vitem) => {
495 assert_eq!(vitem.points[0], Vec4::new(1.0, 0.0, 0.0, 0.0));
496 assert_eq!(vitem.transform.w_axis.truncate(), Vec3::X);
497 }
498 _ => panic!("expected VItem"),
499 }
500 }
501
502 #[test]
503 fn subgroup_operations_keep_the_same_storage_type() {
504 fn require_similarity<T>(_: &Transformed<T, Similarity>) {}
505
506 let mut wrapped = ().transformed(Similarity::IDENTITY);
507 wrapped.shift(DVec3::X).scale_uniform(2.0);
508 require_similarity(&wrapped);
509 assert_eq!(wrapped.transform.scale, 2.0);
510 assert_eq!(wrapped.transform.translation, dvec3(2.0, 0.0, 0.0));
511 }
512
513 #[test]
514 fn explicit_widening_preserves_numeric_transform() {
515 let translation = ().transformed(Translation(dvec3(1.0, 2.0, 3.0)));
516 let rigid: Transformed<_, Rigid> = translation.clone().into();
517 let similarity: Transformed<_, Similarity> = translation.clone().into();
518 let affine: Transformed<_, DAffine3> = translation.into();
519 assert_affine_eq(rigid.transform.into(), affine.transform);
520 assert_affine_eq(similarity.transform.into(), affine.transform);
521
522 let rigid = ().transformed(Rigid {
523 rotation: DQuat::from_rotation_z(0.7),
524 translation: DVec3::Y,
525 });
526 let similarity: Transformed<_, Similarity> = rigid.clone().into();
527 let affine: Transformed<_, DAffine3> = rigid.into();
528 assert_affine_eq(similarity.transform.into(), affine.transform);
529
530 let similarity = ().transformed(Similarity {
531 scale: 2.5,
532 rotation: DQuat::from_rotation_x(0.4),
533 translation: DVec3::Z,
534 });
535 let affine: Transformed<_, DAffine3> = similarity.into();
536 assert_affine_eq(
537 affine.transform,
538 DAffine3::from_scale_rotation_translation(
539 DVec3::splat(2.5),
540 DQuat::from_rotation_x(0.4),
541 DVec3::Z,
542 ),
543 );
544
545 let diagonal = ().transformed(Diag(dvec3(2.0, 0.0, -3.0)));
546 let affine_diagonal: Transformed<_, DAffine3> = diagonal.into();
547 assert_eq!(affine_diagonal.transform.matrix3.y_axis, DVec3::ZERO);
548 assert_eq!(affine_diagonal.transform.matrix3.z_axis.z, -3.0);
549 }
550
551 #[derive(Clone, Debug, PartialEq)]
555 struct StubShape {
556 range: Range<f64>,
558 closed: bool,
559 }
560
561 impl Partial for StubShape {
562 fn get_partial(&self, range: Range<f64>) -> Self {
563 Self {
564 range,
565 closed: false,
566 }
567 }
568
569 fn get_partial_closed(&self, range: Range<f64>) -> Self {
570 Self {
571 range,
572 closed: true,
573 }
574 }
575 }
576
577 impl Empty for StubShape {
578 fn empty() -> Self {
579 Self {
580 range: 0.0..0.0,
581 closed: false,
582 }
583 }
584 }
585
586 #[test]
587 fn partial_forwards_the_range_and_keeps_the_pose() {
588 let wrapped = StubShape {
589 range: 0.0..1.0,
590 closed: false,
591 }
592 .transformed(Translation(DVec3::X));
593
594 let partial = wrapped.get_partial(0.2..0.8);
595 assert_eq!(partial.inner.range, 0.2..0.8);
597 assert!(!partial.inner.closed);
598 assert_eq!(partial.transform, wrapped.transform);
600 assert_eq!(partial.transform, Translation(DVec3::X));
601
602 let closed = wrapped.get_partial_closed(0.3..0.6);
603 assert_eq!(closed.inner.range, 0.3..0.6);
604 assert!(closed.inner.closed);
605 assert_eq!(closed.transform, wrapped.transform);
606 }
607
608 #[test]
609 fn empty_wrapper_carries_an_identity_pose() {
610 let translation: Transformed<StubShape, Translation> = Empty::empty();
611 assert_eq!(translation.inner, StubShape::empty());
612 assert_eq!(translation.transform, Translation(DVec3::ZERO));
613
614 let rigid: Transformed<StubShape, Rigid> = Empty::empty();
615 assert_eq!(rigid.transform, Rigid::identity());
616
617 let similarity: Transformed<StubShape, Similarity> = Empty::empty();
618 assert_eq!(similarity.transform, Similarity::IDENTITY);
619
620 let diagonal: Transformed<StubShape, Diag> = Empty::empty();
621 assert_eq!(diagonal.transform, Diag(dvec3(1.0, 1.0, 1.0)));
622
623 let affine: Transformed<StubShape, DAffine3> = Empty::empty();
624 assert_eq!(affine.transform, DAffine3::IDENTITY);
625 }
626}