session_rust/
xform.rs

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/// A 4x4 column-major transformation matrix in 3D space
8#[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    /// The matrix elements stored in column-major order as a flattened array
16    pub m: [f32; 16],
17}
18
19impl Xform {
20    ///////////////////////////////////////////////////////////////////////////////////////////
21    // Basic Constructors
22    ///////////////////////////////////////////////////////////////////////////////////////////
23
24    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    ///////////////////////////////////////////////////////////////////////////////////////////
66    // Transformations
67    ///////////////////////////////////////////////////////////////////////////////////////////
68
69    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        // Set the origin
202        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    ///////////////////////////////////////////////////////////////////////////////////////////
261    // Apply Transformations
262    ///////////////////////////////////////////////////////////////////////////////////////////
263
264    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    ///////////////////////////////////////////////////////////////////////////////////////////
622    // JSON
623    ///////////////////////////////////////////////////////////////////////////////////////////
624
625    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
649// Implement Display for Xform
650impl 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
676/// Implement Default for Xform to return identity matrix
677impl Default for Xform {
678    fn default() -> Self {
679        Self::identity()
680    }
681}
682
683// Implement Index trait for accessing matrix elements with [(row, col)] syntax
684impl 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        // Column-major order: index = col * 4 + row
691        &self.m[col * 4 + row]
692    }
693}
694
695// Implement IndexMut trait for modifying matrix elements with [(row, col)] syntax
696impl 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        // Column-major order: index = col * 4 + row
701        &mut self.m[col * 4 + row]
702    }
703}
704
705// Implement Mul for matrix multiplication: Xform * Xform = Xform
706impl 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                    // self[i,k] * rhs[k,j]
717                    sum += self[(i, k)] * rhs[(k, j)];
718                }
719                result[(i, j)] = sum;
720            }
721        }
722
723        result
724    }
725}
726
727// Implement Mul for owned matrices
728impl Mul for Xform {
729    type Output = Xform;
730
731    fn mul(self, rhs: Xform) -> Self::Output {
732        &self * &rhs
733    }
734}
735
736// Implement MulAssign for in-place matrix multiplication: xform *= other_xform
737impl 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;