1use crate::{Point, Vector};
2use serde::{ser::Serialize as SerTrait, Deserialize, Serialize};
3use std::fmt;
4use std::ops::{Index, IndexMut, Mul, MulAssign};
5use uuid::Uuid;
6
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
9#[serde(rename = "Xform")]
10pub struct Xform {
11 #[serde(rename = "type")]
12 pub typ: String,
13 pub guid: String,
14 pub name: String,
15 pub m: [f32; 16],
17}
18
19impl Xform {
20 pub fn new() -> Self {
25 Self::identity()
26 }
27
28 pub fn from_matrix(matrix: [f32; 16]) -> Self {
29 Xform {
30 typ: "Xform".to_string(),
31 guid: Uuid::new_v4().to_string(),
32 name: "my_xform".to_string(),
33 m: matrix,
34 }
35 }
36
37 pub fn identity() -> Self {
38 let mut xform = Xform {
39 typ: "Xform".to_string(),
40 guid: Uuid::new_v4().to_string(),
41 name: "my_xform".to_string(),
42 m: [0.0; 16],
43 };
44 xform.m[0] = 1.0;
45 xform.m[5] = 1.0;
46 xform.m[10] = 1.0;
47 xform.m[15] = 1.0;
48 xform
49 }
50
51 pub fn from_cols(col_x: Vector, col_y: Vector, col_z: Vector) -> Self {
52 let mut xform = Self::identity();
53 xform.m[0] = col_x.x();
54 xform.m[1] = col_x.y();
55 xform.m[2] = col_x.z();
56 xform.m[4] = col_y.x();
57 xform.m[5] = col_y.y();
58 xform.m[6] = col_y.z();
59 xform.m[8] = col_z.x();
60 xform.m[9] = col_z.y();
61 xform.m[10] = col_z.z();
62 xform
63 }
64
65 pub fn translation(x: f32, y: f32, z: f32) -> Self {
70 let mut xform = Self::identity();
71 xform.m[12] = x;
72 xform.m[13] = y;
73 xform.m[14] = z;
74 xform
75 }
76
77 pub fn scaling(x: f32, y: f32, z: f32) -> Self {
78 let mut xform = Self::identity();
79 xform.m[0] = x;
80 xform.m[5] = y;
81 xform.m[10] = z;
82 xform
83 }
84
85 pub fn rotation_x(angle_radians: f32) -> Self {
86 let mut xform = Self::identity();
87
88 let cos_angle = angle_radians.cos();
89 let sin_angle = angle_radians.sin();
90
91 xform.m[5] = cos_angle;
92 xform.m[6] = sin_angle;
93 xform.m[9] = -sin_angle;
94 xform.m[10] = cos_angle;
95
96 xform
97 }
98
99 pub fn rotation_y(angle_radians: f32) -> Self {
100 let mut xform = Self::identity();
101
102 let cos_angle = angle_radians.cos();
103 let sin_angle = angle_radians.sin();
104
105 xform.m[0] = cos_angle;
106 xform.m[2] = -sin_angle;
107 xform.m[8] = sin_angle;
108 xform.m[10] = cos_angle;
109
110 xform
111 }
112
113 pub fn rotation_z(angle_radians: f32) -> Self {
114 let mut xform = Self::identity();
115 let cos_angle = angle_radians.cos();
116 let sin_angle = angle_radians.sin();
117
118 xform.m[0] = cos_angle;
119 xform.m[1] = sin_angle;
120 xform.m[4] = -sin_angle;
121 xform.m[5] = cos_angle;
122
123 xform
124 }
125
126 pub fn rotation(axis: &Vector, angle_radians: f32) -> Self {
127 let axis = axis.normalize();
128
129 let mut xform = Self::identity();
130 let cos_angle = angle_radians.cos();
131 let sin_angle = angle_radians.sin();
132 let one_minus_cos = 1.0 - cos_angle;
133
134 let xx = axis.x() * axis.x();
135 let xy = axis.x() * axis.y();
136 let xz = axis.x() * axis.z();
137 let yy = axis.y() * axis.y();
138 let yz = axis.y() * axis.z();
139 let zz = axis.z() * axis.z();
140
141 xform.m[0] = cos_angle + xx * one_minus_cos;
142 xform.m[1] = xy * one_minus_cos + axis.z() * sin_angle;
143 xform.m[2] = xz * one_minus_cos - axis.y() * sin_angle;
144
145 xform.m[4] = xy * one_minus_cos - axis.z() * sin_angle;
146 xform.m[5] = cos_angle + yy * one_minus_cos;
147 xform.m[6] = yz * one_minus_cos + axis.x() * sin_angle;
148
149 xform.m[8] = xz * one_minus_cos + axis.y() * sin_angle;
150 xform.m[9] = yz * one_minus_cos - axis.x() * sin_angle;
151 xform.m[10] = cos_angle + zz * one_minus_cos;
152
153 xform
154 }
155
156 pub fn look_at_rh(eye: &Point, target: &Point, up: &Vector) -> Self {
157 let f = (target.clone() - eye.clone()).normalize();
158 let s = f.cross(&up.normalize()).normalize();
159 let u = s.cross(&f);
160
161 let mut xform = Self::identity();
162
163 xform.m[0] = s.x();
164 xform.m[4] = s.y();
165 xform.m[8] = s.z();
166
167 xform.m[1] = u.x();
168 xform.m[5] = u.y();
169 xform.m[9] = u.z();
170
171 xform.m[2] = -f.x();
172 xform.m[6] = -f.y();
173 xform.m[10] = -f.z();
174
175 xform.m[12] = -s.dot(&Vector::new(eye.x(), eye.y(), eye.z()));
176 xform.m[13] = -u.dot(&Vector::new(eye.x(), eye.y(), eye.z()));
177 xform.m[14] = f.dot(&Vector::new(eye.x(), eye.y(), eye.z()));
178
179 xform
180 }
181
182 pub fn change_basis(origin: &Point, x_axis: &Vector, y_axis: &Vector, z_axis: &Vector) -> Self {
183 let x_axis = x_axis.normalize();
184 let y_axis = y_axis.normalize();
185 let z_axis = z_axis.normalize();
186
187 let mut xform = Self::identity();
188
189 xform.m[0] = x_axis.x();
190 xform.m[1] = x_axis.y();
191 xform.m[2] = x_axis.z();
192
193 xform.m[4] = y_axis.x();
194 xform.m[5] = y_axis.y();
195 xform.m[6] = y_axis.z();
196
197 xform.m[8] = z_axis.x();
198 xform.m[9] = z_axis.y();
199 xform.m[10] = z_axis.z();
200
201 xform.m[12] = origin.x();
203 xform.m[13] = origin.y();
204 xform.m[14] = origin.z();
205
206 xform
207 }
208
209 pub fn inverse(&self) -> Option<Xform> {
210 let a00 = self[(0, 0)];
211 let a01 = self[(0, 1)];
212 let a02 = self[(0, 2)];
213 let a10 = self[(1, 0)];
214 let a11 = self[(1, 1)];
215 let a12 = self[(1, 2)];
216 let a20 = self[(2, 0)];
217 let a21 = self[(2, 1)];
218 let a22 = self[(2, 2)];
219
220 let det = a00 * (a11 * a22 - a12 * a21) - a01 * (a10 * a22 - a12 * a20)
221 + a02 * (a10 * a21 - a11 * a20);
222 if det.abs() < 1e-12 {
223 return None;
224 }
225 let inv_det = 1.0 / det;
226
227 let m00 = (a11 * a22 - a12 * a21) * inv_det;
228 let m01 = (a02 * a21 - a01 * a22) * inv_det;
229 let m02 = (a01 * a12 - a02 * a11) * inv_det;
230 let m10 = (a12 * a20 - a10 * a22) * inv_det;
231 let m11 = (a00 * a22 - a02 * a20) * inv_det;
232 let m12 = (a02 * a10 - a00 * a12) * inv_det;
233 let m20 = (a10 * a21 - a11 * a20) * inv_det;
234 let m21 = (a01 * a20 - a00 * a21) * inv_det;
235 let m22 = (a00 * a11 - a01 * a10) * inv_det;
236
237 let tx = self[(0, 3)];
238 let ty = self[(1, 3)];
239 let tz = self[(2, 3)];
240 let itx = -(m00 * tx + m01 * ty + m02 * tz);
241 let ity = -(m10 * tx + m11 * ty + m12 * tz);
242 let itz = -(m20 * tx + m21 * ty + m22 * tz);
243
244 let mut res = Xform::identity();
245 res[(0, 0)] = m00;
246 res[(0, 1)] = m01;
247 res[(0, 2)] = m02;
248 res[(1, 0)] = m10;
249 res[(1, 1)] = m11;
250 res[(1, 2)] = m12;
251 res[(2, 0)] = m20;
252 res[(2, 1)] = m21;
253 res[(2, 2)] = m22;
254 res[(0, 3)] = itx;
255 res[(1, 3)] = ity;
256 res[(2, 3)] = itz;
257 Some(res)
258 }
259
260 pub fn transformed_point(&self, point: &Point) -> Point {
265 let m = &self.m;
266 let w = m[3] * point.x() + m[7] * point.y() + m[11] * point.z() + m[15];
267 let w_inv = if w.abs() > 1e-10 { 1.0 / w } else { 1.0 };
268
269 Point::new(
270 (m[0] * point.x() + m[4] * point.y() + m[8] * point.z() + m[12]) * w_inv,
271 (m[1] * point.x() + m[5] * point.y() + m[9] * point.z() + m[13]) * w_inv,
272 (m[2] * point.x() + m[6] * point.y() + m[10] * point.z() + m[14]) * w_inv,
273 )
274 }
275
276 pub fn transformed_vector(&self, vector: &Vector) -> Vector {
277 let m = &self.m;
278
279 Vector::new(
280 m[0] * vector.x() + m[4] * vector.y() + m[8] * vector.z(),
281 m[1] * vector.x() + m[5] * vector.y() + m[9] * vector.z(),
282 m[2] * vector.x() + m[6] * vector.y() + m[10] * vector.z(),
283 )
284 }
285
286 pub fn transform_point(&self, point: &mut Point) {
287 let m = &self.m;
288 let x = point[0];
289 let y = point[1];
290 let z = point[2];
291 let w = m[3] * x + m[7] * y + m[11] * z + m[15];
292 let w_inv = if w.abs() > 1e-10 { 1.0 / w } else { 1.0 };
293
294 point[0] = (m[0] * x + m[4] * y + m[8] * z + m[12]) * w_inv;
295 point[1] = (m[1] * x + m[5] * y + m[9] * z + m[13]) * w_inv;
296 point[2] = (m[2] * x + m[6] * y + m[10] * z + m[14]) * w_inv;
297 }
298
299 pub fn transform_vector(&self, vector: &mut Vector) {
300 let m = &self.m;
301 let x = vector[0];
302 let y = vector[1];
303 let z = vector[2];
304
305 vector[0] = m[0] * x + m[4] * y + m[8] * z;
306 vector[1] = m[1] * x + m[5] * y + m[9] * z;
307 vector[2] = m[2] * x + m[6] * y + m[10] * z;
308 }
309
310 pub fn x(&self) -> Vector {
311 Vector::new(self.m[0], self.m[1], self.m[2])
312 }
313
314 pub fn y(&self) -> Vector {
315 Vector::new(self.m[4], self.m[5], self.m[6])
316 }
317
318 pub fn z(&self) -> Vector {
319 Vector::new(self.m[8], self.m[9], self.m[10])
320 }
321
322 pub fn is_identity(&self) -> bool {
323 let identity = Xform::identity();
324 for i in 0..16 {
325 if (self.m[i] - identity.m[i]).abs() > 1e-10 {
326 return false;
327 }
328 }
329 true
330 }
331
332 #[allow(clippy::too_many_arguments)]
333 pub fn change_basis_alt(
334 origin_1: &Point,
335 x_axis_1: &Vector,
336 y_axis_1: &Vector,
337 z_axis_1: &Vector,
338 origin_0: &Point,
339 x_axis_0: &Vector,
340 y_axis_0: &Vector,
341 z_axis_0: &Vector,
342 ) -> Self {
343 let a = x_axis_1.dot(y_axis_1);
344 let b = x_axis_1.dot(z_axis_1);
345 let c = y_axis_1.dot(z_axis_1);
346
347 let mut r = [
348 [
349 x_axis_1.dot(x_axis_1),
350 a,
351 b,
352 x_axis_1.dot(x_axis_0),
353 x_axis_1.dot(y_axis_0),
354 x_axis_1.dot(z_axis_0),
355 ],
356 [
357 a,
358 y_axis_1.dot(y_axis_1),
359 c,
360 y_axis_1.dot(x_axis_0),
361 y_axis_1.dot(y_axis_0),
362 y_axis_1.dot(z_axis_0),
363 ],
364 [
365 b,
366 c,
367 z_axis_1.dot(z_axis_1),
368 z_axis_1.dot(x_axis_0),
369 z_axis_1.dot(y_axis_0),
370 z_axis_1.dot(z_axis_0),
371 ],
372 ];
373
374 let mut i0 = if r[0][0] >= r[1][1] { 0 } else { 1 };
375 if r[2][2] > r[i0][i0] {
376 i0 = 2;
377 }
378 let i1 = (i0 + 1) % 3;
379 let i2 = (i1 + 1) % 3;
380
381 if r[i0][i0] == 0.0 {
382 return Self::identity();
383 }
384
385 let d = 1.0 / r[i0][i0];
386 for j in 0..6 {
387 r[i0][j] *= d;
388 }
389 r[i0][i0] = 1.0;
390
391 if r[i1][i0] != 0.0 {
392 let d = -r[i1][i0];
393 for j in 0..6 {
394 r[i1][j] += d * r[i0][j];
395 }
396 r[i1][i0] = 0.0;
397 }
398 if r[i2][i0] != 0.0 {
399 let d = -r[i2][i0];
400 for j in 0..6 {
401 r[i2][j] += d * r[i0][j];
402 }
403 r[i2][i0] = 0.0;
404 }
405
406 let (i1, i2) = if r[i1][i1].abs() < r[i2][i2].abs() {
407 (i2, i1)
408 } else {
409 (i1, i2)
410 };
411 if r[i1][i1] == 0.0 {
412 return Self::identity();
413 }
414
415 let d = 1.0 / r[i1][i1];
416 for j in 0..6 {
417 r[i1][j] *= d;
418 }
419 r[i1][i1] = 1.0;
420
421 if r[i0][i1] != 0.0 {
422 let d = -r[i0][i1];
423 for j in 0..6 {
424 r[i0][j] += d * r[i1][j];
425 }
426 r[i0][i1] = 0.0;
427 }
428 if r[i2][i1] != 0.0 {
429 let d = -r[i2][i1];
430 for j in 0..6 {
431 r[i2][j] += d * r[i1][j];
432 }
433 r[i2][i1] = 0.0;
434 }
435
436 if r[i2][i2] == 0.0 {
437 return Self::identity();
438 }
439
440 let d = 1.0 / r[i2][i2];
441 for j in 0..6 {
442 r[i2][j] *= d;
443 }
444 r[i2][i2] = 1.0;
445
446 if r[i0][i2] != 0.0 {
447 let d = -r[i0][i2];
448 for j in 0..6 {
449 r[i0][j] += d * r[i2][j];
450 }
451 r[i0][i2] = 0.0;
452 }
453 if r[i1][i2] != 0.0 {
454 let d = -r[i1][i2];
455 for j in 0..6 {
456 r[i1][j] += d * r[i2][j];
457 }
458 r[i1][i2] = 0.0;
459 }
460
461 let mut m_xform = Self::identity();
462 m_xform.m[0] = r[0][3];
463 m_xform.m[4] = r[0][4];
464 m_xform.m[8] = r[0][5];
465 m_xform.m[1] = r[1][3];
466 m_xform.m[5] = r[1][4];
467 m_xform.m[9] = r[1][5];
468 m_xform.m[2] = r[2][3];
469 m_xform.m[6] = r[2][4];
470 m_xform.m[10] = r[2][5];
471
472 let t0 = Self::translation(-origin_1.x(), -origin_1.y(), -origin_1.z());
473 let t2 = Self::translation(origin_0.x(), origin_0.y(), origin_0.z());
474 &t2 * &(&m_xform * &t0)
475 }
476
477 #[allow(clippy::too_many_arguments)]
478 pub fn plane_to_plane(
479 origin_0: &Point,
480 x_axis_0: &Vector,
481 y_axis_0: &Vector,
482 z_axis_0: &Vector,
483 origin_1: &Point,
484 x_axis_1: &Vector,
485 y_axis_1: &Vector,
486 z_axis_1: &Vector,
487 ) -> Self {
488 let mut x0 = x_axis_0.clone();
489 let mut y0 = y_axis_0.clone();
490 let mut z0 = z_axis_0.clone();
491 let mut x1 = x_axis_1.clone();
492 let mut y1 = y_axis_1.clone();
493 let mut z1 = z_axis_1.clone();
494 x0.normalize_self();
495 y0.normalize_self();
496 z0.normalize_self();
497 x1.normalize_self();
498 y1.normalize_self();
499 z1.normalize_self();
500
501 let t0 = Self::translation(-origin_0.x(), -origin_0.y(), -origin_0.z());
502
503 let mut f0 = Self::identity();
504 f0.m[0] = x0.x();
505 f0.m[1] = x0.y();
506 f0.m[2] = x0.z();
507 f0.m[4] = y0.x();
508 f0.m[5] = y0.y();
509 f0.m[6] = y0.z();
510 f0.m[8] = z0.x();
511 f0.m[9] = z0.y();
512 f0.m[10] = z0.z();
513
514 let mut f1 = Self::identity();
515 f1.m[0] = x1.x();
516 f1.m[4] = x1.y();
517 f1.m[8] = x1.z();
518 f1.m[1] = y1.x();
519 f1.m[5] = y1.y();
520 f1.m[9] = y1.z();
521 f1.m[2] = z1.x();
522 f1.m[6] = z1.y();
523 f1.m[10] = z1.z();
524
525 let r = &f1 * &f0;
526 let t1 = Self::translation(origin_1.x(), origin_1.y(), origin_1.z());
527 &t1 * &(&r * &t0)
528 }
529
530 pub fn plane_to_xy(origin: &Point, x_axis: &Vector, y_axis: &Vector, z_axis: &Vector) -> Self {
531 let mut x = x_axis.clone();
532 let mut y = y_axis.clone();
533 let mut z = z_axis.clone();
534 x.normalize_self();
535 y.normalize_self();
536 z.normalize_self();
537
538 let t = Self::translation(-origin.x(), -origin.y(), -origin.z());
539 let mut f = Self::identity();
540 f.m[0] = x.x();
541 f.m[1] = x.y();
542 f.m[2] = x.z();
543 f.m[4] = y.x();
544 f.m[5] = y.y();
545 f.m[6] = y.z();
546 f.m[8] = z.x();
547 f.m[9] = z.y();
548 f.m[10] = z.z();
549 &f * &t
550 }
551
552 pub fn xy_to_plane(origin: &Point, x_axis: &Vector, y_axis: &Vector, z_axis: &Vector) -> Self {
553 let mut x = x_axis.clone();
554 let mut y = y_axis.clone();
555 let mut z = z_axis.clone();
556 x.normalize_self();
557 y.normalize_self();
558 z.normalize_self();
559
560 let mut f = Self::identity();
561 f.m[0] = x.x();
562 f.m[4] = y.x();
563 f.m[8] = z.x();
564 f.m[1] = x.y();
565 f.m[5] = y.y();
566 f.m[9] = z.y();
567 f.m[2] = x.z();
568 f.m[6] = y.z();
569 f.m[10] = z.z();
570
571 let t = Self::translation(origin.x(), origin.y(), origin.z());
572 &t * &f
573 }
574
575 pub fn scale_xyz(scale_x: f32, scale_y: f32, scale_z: f32) -> Self {
576 let mut xform = Self::identity();
577 xform.m[0] = scale_x;
578 xform.m[5] = scale_y;
579 xform.m[10] = scale_z;
580 xform
581 }
582
583 pub fn scale_uniform(origin: &Point, scale_value: f32) -> Self {
584 let t0 = Self::translation(-origin.x(), -origin.y(), -origin.z());
585 let t1 = Self::scaling(scale_value, scale_value, scale_value);
586 let t2 = Self::translation(origin.x(), origin.y(), origin.z());
587 &t2 * &(&t1 * &t0)
588 }
589
590 pub fn scale_non_uniform(origin: &Point, scale_x: f32, scale_y: f32, scale_z: f32) -> Self {
591 let t0 = Self::translation(-origin.x(), -origin.y(), -origin.z());
592 let t1 = Self::scale_xyz(scale_x, scale_y, scale_z);
593 let t2 = Self::translation(origin.x(), origin.y(), origin.z());
594 &t2 * &(&t1 * &t0)
595 }
596
597 pub fn axis_rotation(angle: f32, axis: &Vector) -> Self {
598 let c = angle.cos();
599 let s = angle.sin();
600 let ux = axis.x();
601 let uy = axis.y();
602 let uz = axis.z();
603 let t = 1.0 - c;
604
605 let mut xform = Self::identity();
606 xform.m[0] = t * ux * ux + c;
607 xform.m[4] = t * ux * uy - uz * s;
608 xform.m[8] = t * ux * uz + uy * s;
609
610 xform.m[1] = t * ux * uy + uz * s;
611 xform.m[5] = t * uy * uy + c;
612 xform.m[9] = t * uy * uz - ux * s;
613
614 xform.m[2] = t * ux * uz - uy * s;
615 xform.m[6] = t * uy * uz + ux * s;
616 xform.m[10] = t * uz * uz + c;
617
618 xform
619 }
620
621 pub fn jsondump(&self) -> Result<String, Box<dyn std::error::Error>> {
626 let mut buf = Vec::new();
627 let formatter = serde_json::ser::PrettyFormatter::with_indent(b" ");
628 let mut ser = serde_json::Serializer::with_formatter(&mut buf, formatter);
629 SerTrait::serialize(self, &mut ser)?;
630 Ok(String::from_utf8(buf)?)
631 }
632
633 pub fn jsonload(json_data: &str) -> Result<Self, Box<dyn std::error::Error>> {
634 Ok(serde_json::from_str(json_data)?)
635 }
636
637 pub fn to_json(&self, filepath: &str) -> Result<(), Box<dyn std::error::Error>> {
638 let json = self.jsondump()?;
639 std::fs::write(filepath, json)?;
640 Ok(())
641 }
642
643 pub fn from_json(filepath: &str) -> Result<Self, Box<dyn std::error::Error>> {
644 let json = std::fs::read_to_string(filepath)?;
645 Self::jsonload(&json)
646 }
647}
648
649impl fmt::Display for Xform {
651 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
652 writeln!(f, "Transform Matrix:")?;
653 writeln!(
654 f,
655 "[{:.4}, {:.4}, {:.4}, {:.4}]",
656 self.m[0], self.m[4], self.m[8], self.m[12]
657 )?;
658 writeln!(
659 f,
660 "[{:.4}, {:.4}, {:.4}, {:.4}]",
661 self.m[1], self.m[5], self.m[9], self.m[13]
662 )?;
663 writeln!(
664 f,
665 "[{:.4}, {:.4}, {:.4}, {:.4}]",
666 self.m[2], self.m[6], self.m[10], self.m[14]
667 )?;
668 write!(
669 f,
670 "[{:.4}, {:.4}, {:.4}, {:.4}]",
671 self.m[3], self.m[7], self.m[11], self.m[15]
672 )
673 }
674}
675
676impl Default for Xform {
678 fn default() -> Self {
679 Self::identity()
680 }
681}
682
683impl Index<(usize, usize)> for Xform {
685 type Output = f32;
686
687 fn index(&self, idx: (usize, usize)) -> &Self::Output {
688 let (row, col) = idx;
689 assert!(row < 4 && col < 4, "Index out of bounds: ({row}, {col})");
690 &self.m[col * 4 + row]
692 }
693}
694
695impl IndexMut<(usize, usize)> for Xform {
697 fn index_mut(&mut self, idx: (usize, usize)) -> &mut Self::Output {
698 let (row, col) = idx;
699 assert!(row < 4 && col < 4, "Index out of bounds: ({row}, {col})");
700 &mut self.m[col * 4 + row]
702 }
703}
704
705impl Mul for &Xform {
707 type Output = Xform;
708
709 fn mul(self, rhs: &Xform) -> Self::Output {
710 let mut result = Xform::identity();
711
712 for i in 0..4 {
713 for j in 0..4 {
714 let mut sum = 0.0;
715 for k in 0..4 {
716 sum += self[(i, k)] * rhs[(k, j)];
718 }
719 result[(i, j)] = sum;
720 }
721 }
722
723 result
724 }
725}
726
727impl Mul for Xform {
729 type Output = Xform;
730
731 fn mul(self, rhs: Xform) -> Self::Output {
732 &self * &rhs
733 }
734}
735
736impl MulAssign for Xform {
738 fn mul_assign(&mut self, rhs: Self) {
739 *self = &*self * &rhs;
740 }
741}
742
743#[cfg(test)]
744#[path = "xform_test.rs"]
745mod tests;