1#![allow(clippy::needless_range_loop)]
6pub use nalgebra::{Matrix3, Matrix4, Point3, Unit, UnitQuaternion, Vector3, Vector4};
7
8use super::geometry::{quat_exp, quat_log};
9use super::types::*;
10
11pub type Real = f64;
13pub type Vec3 = Vector3<Real>;
15pub type Vec4 = Vector4<Real>;
17pub type Mat3 = Matrix3<Real>;
19pub type Mat4 = Matrix4<Real>;
21pub type Quat = UnitQuaternion<Real>;
23#[allow(dead_code)]
27pub fn quat_from_axis_angle(axis: &Vec3, angle: Real) -> Quat {
28 UnitQuaternion::from_axis_angle(&Unit::new_normalize(*axis), angle)
29}
30#[allow(dead_code)]
35pub fn quat_to_euler(q: &Quat) -> (Real, Real, Real) {
36 let (roll, pitch, yaw) = q.euler_angles();
37 (roll, pitch, yaw)
38}
39#[allow(dead_code)]
43pub fn quat_from_euler(roll: Real, pitch: Real, yaw: Real) -> Quat {
44 UnitQuaternion::from_euler_angles(roll, pitch, yaw)
45}
46#[allow(dead_code)]
48pub fn quat_slerp(a: &Quat, b: &Quat, t: Real) -> Quat {
49 a.slerp(b, t)
50}
51#[allow(dead_code)]
53pub fn mat3_determinant(m: &Mat3) -> Real {
54 m.determinant()
55}
56#[allow(dead_code)]
60pub fn mat3_inverse(m: &Mat3) -> Option<Mat3> {
61 m.try_inverse()
62}
63#[allow(dead_code)]
68pub fn mat3_eigenvalues_symmetric(m: &Mat3) -> [Real; 3] {
69 let eigen = m.symmetric_eigen();
70 let mut vals = [
71 eigen.eigenvalues[0],
72 eigen.eigenvalues[1],
73 eigen.eigenvalues[2],
74 ];
75 vals.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
76 vals
77}
78#[allow(dead_code)]
80pub fn mat3_trace(m: &Mat3) -> Real {
81 m.trace()
82}
83#[allow(dead_code)]
85pub fn mat3_frobenius_norm(m: &Mat3) -> Real {
86 let mut sum = 0.0;
87 for i in 0..3 {
88 for j in 0..3 {
89 sum += m[(i, j)] * m[(i, j)];
90 }
91 }
92 sum.sqrt()
93}
94#[allow(dead_code)]
96pub fn skew_symmetric(v: &Vec3) -> Mat3 {
97 Mat3::new(0.0, -v.z, v.y, v.z, 0.0, -v.x, -v.y, v.x, 0.0)
98}
99#[allow(dead_code)]
106pub fn perspective(fov_y: Real, aspect: Real, near: Real, far: Real) -> Mat4 {
107 let f = 1.0 / (fov_y / 2.0).tan();
108 let nf = 1.0 / (near - far);
109 Mat4::new(
110 f / aspect,
111 0.0,
112 0.0,
113 0.0,
114 0.0,
115 f,
116 0.0,
117 0.0,
118 0.0,
119 0.0,
120 (far + near) * nf,
121 2.0 * far * near * nf,
122 0.0,
123 0.0,
124 -1.0,
125 0.0,
126 )
127}
128#[allow(dead_code)]
134pub fn look_at(eye: &Vec3, target: &Vec3, up: &Vec3) -> Mat4 {
135 let f = (target - eye).normalize();
136 let s = f.cross(up).normalize();
137 let u = s.cross(&f);
138 Mat4::new(
139 s.x,
140 s.y,
141 s.z,
142 -s.dot(eye),
143 u.x,
144 u.y,
145 u.z,
146 -u.dot(eye),
147 -f.x,
148 -f.y,
149 -f.z,
150 f.dot(eye),
151 0.0,
152 0.0,
153 0.0,
154 1.0,
155 )
156}
157#[allow(dead_code, clippy::too_many_arguments)]
159pub fn orthographic(
160 left: Real,
161 right: Real,
162 bottom: Real,
163 top: Real,
164 near: Real,
165 far: Real,
166) -> Mat4 {
167 let rml = right - left;
168 let tmb = top - bottom;
169 let fmn = far - near;
170 Mat4::new(
171 2.0 / rml,
172 0.0,
173 0.0,
174 -(right + left) / rml,
175 0.0,
176 2.0 / tmb,
177 0.0,
178 -(top + bottom) / tmb,
179 0.0,
180 0.0,
181 -2.0 / fmn,
182 -(far + near) / fmn,
183 0.0,
184 0.0,
185 0.0,
186 1.0,
187 )
188}
189#[allow(dead_code)]
191pub fn vec4(x: Real, y: Real, z: Real, w: Real) -> Vec4 {
192 Vec4::new(x, y, z, w)
193}
194#[allow(dead_code)]
196pub fn vec4_point(v: &Vec3) -> Vec4 {
197 Vec4::new(v.x, v.y, v.z, 1.0)
198}
199#[allow(dead_code)]
201pub fn vec4_direction(v: &Vec3) -> Vec4 {
202 Vec4::new(v.x, v.y, v.z, 0.0)
203}
204#[allow(dead_code)]
208pub fn vec4_to_vec3(v: &Vec4) -> Option<Vec3> {
209 if v.w.abs() < 1e-10 {
210 return None;
211 }
212 Some(Vec3::new(v.x / v.w, v.y / v.w, v.z / v.w))
213}
214#[allow(dead_code)]
219pub fn plane_plane_intersection(a: &Plane, b: &Plane) -> Option<(Vec3, Vec3)> {
220 let dir = a.normal.cross(&b.normal);
221 let len_sq = dir.norm_squared();
222 if len_sq < 1e-10 {
223 return None;
224 }
225 let p = (b.normal.cross(&dir) * a.distance + dir.cross(&a.normal) * b.distance) / len_sq;
226 Some((p, dir.normalize()))
227}
228#[allow(dead_code)]
232pub fn three_plane_intersection(a: &Plane, b: &Plane, c: &Plane) -> Option<Vec3> {
233 let denom = a.normal.dot(&b.normal.cross(&c.normal));
234 if denom.abs() < 1e-10 {
235 return None;
236 }
237 let p = (b.normal.cross(&c.normal) * a.distance
238 + c.normal.cross(&a.normal) * b.distance
239 + a.normal.cross(&b.normal) * c.distance)
240 / denom;
241 Some(p)
242}
243#[allow(dead_code)]
251pub fn polar_decomposition(m: &Mat3, max_iter: usize) -> Option<(Mat3, Mat3)> {
252 let mut r = *m;
253 for _ in 0..max_iter {
254 let r_inv_t = r.try_inverse()?.transpose();
255 let r_new = (r + r_inv_t) * 0.5;
256 let diff = (r_new - r).norm();
257 r = r_new;
258 if diff < 1e-12 {
259 break;
260 }
261 }
262 let s = r.transpose() * m;
263 Some((r, s))
264}
265#[allow(dead_code)]
272pub fn symmetric_eigen3(m: &Mat3) -> ([Real; 3], Mat3) {
273 let mut a = *m;
274 let mut v = Mat3::identity();
275 for _ in 0..100 {
276 let mut max_val = 0.0_f64;
277 let mut p = 0;
278 let mut q = 1;
279 for i in 0..3 {
280 for j in (i + 1)..3 {
281 if a[(i, j)].abs() > max_val {
282 max_val = a[(i, j)].abs();
283 p = i;
284 q = j;
285 }
286 }
287 }
288 if max_val < 1e-12 {
289 break;
290 }
291 let theta = (a[(q, q)] - a[(p, p)]) / (2.0 * a[(p, q)]);
292 let t = if theta >= 0.0 {
293 1.0 / (theta + (1.0 + theta * theta).sqrt())
294 } else {
295 1.0 / (theta - (1.0 + theta * theta).sqrt())
296 };
297 let cos = 1.0 / (1.0 + t * t).sqrt();
298 let sin = t * cos;
299 let a_pp = a[(p, p)];
300 let a_qq = a[(q, q)];
301 let a_pq = a[(p, q)];
302 a[(p, p)] = cos * cos * a_pp - 2.0 * sin * cos * a_pq + sin * sin * a_qq;
303 a[(q, q)] = sin * sin * a_pp + 2.0 * sin * cos * a_pq + cos * cos * a_qq;
304 a[(p, q)] = 0.0;
305 a[(q, p)] = 0.0;
306 for r in 0..3 {
307 if r != p && r != q {
308 let a_rp = a[(r, p)];
309 let a_rq = a[(r, q)];
310 a[(r, p)] = cos * a_rp - sin * a_rq;
311 a[(p, r)] = a[(r, p)];
312 a[(r, q)] = sin * a_rp + cos * a_rq;
313 a[(q, r)] = a[(r, q)];
314 }
315 }
316 for r in 0..3 {
317 let v_rp = v[(r, p)];
318 let v_rq = v[(r, q)];
319 v[(r, p)] = cos * v_rp - sin * v_rq;
320 v[(r, q)] = sin * v_rp + cos * v_rq;
321 }
322 }
323 ([a[(0, 0)], a[(1, 1)], a[(2, 2)]], v)
324}
325#[allow(dead_code)]
329pub fn sh_y00() -> Real {
330 0.5 / std::f64::consts::PI.sqrt()
331}
332#[allow(dead_code)]
336pub fn sh_y1m1(dir: &Vec3) -> Real {
337 let len = dir.norm();
338 if len < 1e-12 {
339 return 0.0;
340 }
341 let y = dir.y / len;
342 (3.0 / (4.0 * std::f64::consts::PI)).sqrt() * y
343}
344#[allow(dead_code)]
348pub fn sh_y10(dir: &Vec3) -> Real {
349 let len = dir.norm();
350 if len < 1e-12 {
351 return 0.0;
352 }
353 let z = dir.z / len;
354 (3.0 / (4.0 * std::f64::consts::PI)).sqrt() * z
355}
356#[allow(dead_code)]
360pub fn sh_y11(dir: &Vec3) -> Real {
361 let len = dir.norm();
362 if len < 1e-12 {
363 return 0.0;
364 }
365 let x = dir.x / len;
366 (3.0 / (4.0 * std::f64::consts::PI)).sqrt() * x
367}
368#[allow(dead_code)]
374pub fn sh_project_l1(radiance_fn: impl Fn(&Vec3) -> Real, n: usize) -> [Real; 4] {
375 let golden = (1.0 + 5.0_f64.sqrt()) / 2.0;
376 let mut coeffs = [0.0_f64; 4];
377 let weight = 4.0 * std::f64::consts::PI / n as Real;
378 for i in 0..n {
379 let theta = (1.0 - 2.0 * (i as Real + 0.5) / n as Real).acos();
380 let phi = 2.0 * std::f64::consts::PI * (i as Real / golden).fract();
381 let dir = Vec3::new(
382 theta.sin() * phi.cos(),
383 theta.sin() * phi.sin(),
384 theta.cos(),
385 );
386 let rad = radiance_fn(&dir);
387 coeffs[0] += rad * sh_y00() * weight;
388 coeffs[1] += rad * sh_y1m1(&dir) * weight;
389 coeffs[2] += rad * sh_y10(&dir) * weight;
390 coeffs[3] += rad * sh_y11(&dir) * weight;
391 }
392 coeffs
393}
394#[allow(dead_code)]
398pub fn dual_differentiate(f: impl Fn(Dual) -> Dual, x: Real) -> Real {
399 f(Dual::variable(x)).du
400}
401#[allow(dead_code)]
404pub fn hermite_interpolate(p0: &Vec3, m0: &Vec3, p1: &Vec3, m1: &Vec3, t: Real) -> Vec3 {
405 let t2 = t * t;
406 let t3 = t2 * t;
407 let h00 = 2.0 * t3 - 3.0 * t2 + 1.0;
408 let h10 = t3 - 2.0 * t2 + t;
409 let h01 = -2.0 * t3 + 3.0 * t2;
410 let h11 = t3 - t2;
411 p0 * h00 + m0 * h10 + p1 * h01 + m1 * h11
412}
413#[allow(dead_code)]
418pub fn catmull_rom(p0: &Vec3, p1: &Vec3, p2: &Vec3, p3: &Vec3, t: Real) -> Vec3 {
419 let m1 = (p2 - p0) * 0.5;
420 let m2 = (p3 - p1) * 0.5;
421 hermite_interpolate(p1, &m1, p2, &m2, t)
422}
423#[allow(dead_code)]
427pub fn bezier_cubic(p0: &Vec3, p1: &Vec3, p2: &Vec3, p3: &Vec3, t: Real) -> Vec3 {
428 let mt = 1.0 - t;
429 p0 * (mt * mt * mt) + p1 * (3.0 * mt * mt * t) + p2 * (3.0 * mt * t * t) + p3 * (t * t * t)
430}
431#[allow(dead_code)]
436pub fn bspline_basis3(t: Real) -> [Real; 4] {
437 let t2 = t * t;
438 let t3 = t2 * t;
439 let mt = 1.0 - t;
440 [
441 mt * mt * mt / 6.0,
442 (3.0 * t3 - 6.0 * t2 + 4.0) / 6.0,
443 (-3.0 * t3 + 3.0 * t2 + 3.0 * t + 1.0) / 6.0,
444 t3 / 6.0,
445 ]
446}
447#[allow(dead_code)]
449pub fn mat3_transpose(m: [[f64; 3]; 3]) -> [[f64; 3]; 3] {
450 [
451 [m[0][0], m[1][0], m[2][0]],
452 [m[0][1], m[1][1], m[2][1]],
453 [m[0][2], m[1][2], m[2][2]],
454 ]
455}
456#[allow(dead_code)]
458pub fn mat3_mul_vec3(m: [[f64; 3]; 3], v: [f64; 3]) -> [f64; 3] {
459 [
460 m[0][0] * v[0] + m[0][1] * v[1] + m[0][2] * v[2],
461 m[1][0] * v[0] + m[1][1] * v[1] + m[1][2] * v[2],
462 m[2][0] * v[0] + m[2][1] * v[1] + m[2][2] * v[2],
463 ]
464}
465#[allow(dead_code)]
467pub fn mat3_mul_mat3(a: [[f64; 3]; 3], b: [[f64; 3]; 3]) -> [[f64; 3]; 3] {
468 let mut r = [[0.0_f64; 3]; 3];
469 for i in 0..3 {
470 for j in 0..3 {
471 for k in 0..3 {
472 r[i][j] += a[i][k] * b[k][j];
473 }
474 }
475 }
476 r
477}
478#[allow(dead_code)]
480pub fn mat3_det(m: [[f64; 3]; 3]) -> f64 {
481 m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1])
482 - m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0])
483 + m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0])
484}
485#[allow(dead_code)]
487pub fn mat3_arr_inverse(m: [[f64; 3]; 3]) -> Option<[[f64; 3]; 3]> {
488 let det = mat3_det(m);
489 if det.abs() < 1e-14 {
490 return None;
491 }
492 let inv_det = 1.0 / det;
493 Some([
494 [
495 (m[1][1] * m[2][2] - m[1][2] * m[2][1]) * inv_det,
496 (m[0][2] * m[2][1] - m[0][1] * m[2][2]) * inv_det,
497 (m[0][1] * m[1][2] - m[0][2] * m[1][1]) * inv_det,
498 ],
499 [
500 (m[1][2] * m[2][0] - m[1][0] * m[2][2]) * inv_det,
501 (m[0][0] * m[2][2] - m[0][2] * m[2][0]) * inv_det,
502 (m[0][2] * m[1][0] - m[0][0] * m[1][2]) * inv_det,
503 ],
504 [
505 (m[1][0] * m[2][1] - m[1][1] * m[2][0]) * inv_det,
506 (m[0][1] * m[2][0] - m[0][0] * m[2][1]) * inv_det,
507 (m[0][0] * m[1][1] - m[0][1] * m[1][0]) * inv_det,
508 ],
509 ])
510}
511#[allow(dead_code)]
513pub fn mat3_from_cols(c0: [f64; 3], c1: [f64; 3], c2: [f64; 3]) -> [[f64; 3]; 3] {
514 [
515 [c0[0], c1[0], c2[0]],
516 [c0[1], c1[1], c2[1]],
517 [c0[2], c1[2], c2[2]],
518 ]
519}
520#[allow(dead_code)]
522pub fn mat3_outer_product(a: [f64; 3], b: [f64; 3]) -> [[f64; 3]; 3] {
523 [
524 [a[0] * b[0], a[0] * b[1], a[0] * b[2]],
525 [a[1] * b[0], a[1] * b[1], a[1] * b[2]],
526 [a[2] * b[0], a[2] * b[1], a[2] * b[2]],
527 ]
528}
529#[allow(dead_code)]
533pub fn quat_arr_from_axis_angle(axis: [f64; 3], angle: f64) -> [f64; 4] {
534 let half = angle * 0.5;
535 let s = half.sin();
536 [axis[0] * s, axis[1] * s, axis[2] * s, half.cos()]
537}
538#[allow(dead_code)]
540pub fn quat_multiply(p: [f64; 4], q: [f64; 4]) -> [f64; 4] {
541 let [px, py, pz, pw] = p;
542 let [qx, qy, qz, qw] = q;
543 [
544 pw * qx + px * qw + py * qz - pz * qy,
545 pw * qy - px * qz + py * qw + pz * qx,
546 pw * qz + px * qy - py * qx + pz * qw,
547 pw * qw - px * qx - py * qy - pz * qz,
548 ]
549}
550#[allow(dead_code)]
552pub fn quat_to_mat3(q: [f64; 4]) -> [[f64; 3]; 3] {
553 let [x, y, z, w] = q;
554 let x2 = x * x;
555 let y2 = y * y;
556 let z2 = z * z;
557 let xy = x * y;
558 let xz = x * z;
559 let yz = y * z;
560 let wx = w * x;
561 let wy = w * y;
562 let wz = w * z;
563 [
564 [1.0 - 2.0 * (y2 + z2), 2.0 * (xy - wz), 2.0 * (xz + wy)],
565 [2.0 * (xy + wz), 1.0 - 2.0 * (x2 + z2), 2.0 * (yz - wx)],
566 [2.0 * (xz - wy), 2.0 * (yz + wx), 1.0 - 2.0 * (x2 + y2)],
567 ]
568}
569#[allow(dead_code)]
571pub fn quat_arr_slerp(p: [f64; 4], q: [f64; 4], t: f64) -> [f64; 4] {
572 let dot = p[0] * q[0] + p[1] * q[1] + p[2] * q[2] + p[3] * q[3];
573 let (q2, dot2) = if dot < 0.0 {
574 ([-q[0], -q[1], -q[2], -q[3]], -dot)
575 } else {
576 (q, dot)
577 };
578 let dot2 = dot2.clamp(-1.0, 1.0);
579 if dot2 > 0.9995 {
580 let r = [
581 p[0] + t * (q2[0] - p[0]),
582 p[1] + t * (q2[1] - p[1]),
583 p[2] + t * (q2[2] - p[2]),
584 p[3] + t * (q2[3] - p[3]),
585 ];
586 let norm = (r[0] * r[0] + r[1] * r[1] + r[2] * r[2] + r[3] * r[3]).sqrt();
587 [r[0] / norm, r[1] / norm, r[2] / norm, r[3] / norm]
588 } else {
589 let theta = dot2.acos();
590 let sin_theta = theta.sin();
591 let s0 = ((1.0 - t) * theta).sin() / sin_theta;
592 let s1 = (t * theta).sin() / sin_theta;
593 [
594 s0 * p[0] + s1 * q2[0],
595 s0 * p[1] + s1 * q2[1],
596 s0 * p[2] + s1 * q2[2],
597 s0 * p[3] + s1 * q2[3],
598 ]
599 }
600}
601#[allow(dead_code)]
603pub fn vec3_project(a: [f64; 3], onto: [f64; 3]) -> [f64; 3] {
604 let denom = onto[0] * onto[0] + onto[1] * onto[1] + onto[2] * onto[2];
605 if denom < 1e-30 {
606 return [0.0; 3];
607 }
608 let s = (a[0] * onto[0] + a[1] * onto[1] + a[2] * onto[2]) / denom;
609 [onto[0] * s, onto[1] * s, onto[2] * s]
610}
611#[allow(dead_code)]
613pub fn vec3_reflect(v: [f64; 3], n: [f64; 3]) -> [f64; 3] {
614 let dot2 = 2.0 * (v[0] * n[0] + v[1] * n[1] + v[2] * n[2]);
615 [v[0] - dot2 * n[0], v[1] - dot2 * n[1], v[2] - dot2 * n[2]]
616}
617#[allow(dead_code)]
619pub fn vec3_lerp(a: [f64; 3], b: [f64; 3], t: f64) -> [f64; 3] {
620 [
621 a[0] + t * (b[0] - a[0]),
622 a[1] + t * (b[1] - a[1]),
623 a[2] + t * (b[2] - a[2]),
624 ]
625}
626#[allow(dead_code)]
630pub fn plane_from_point_normal(p: [f64; 3], n: [f64; 3]) -> [f64; 4] {
631 let d = n[0] * p[0] + n[1] * p[1] + n[2] * p[2];
632 [n[0], n[1], n[2], d]
633}
634#[allow(dead_code)]
638pub fn plane_signed_dist(plane: [f64; 4], point: [f64; 3]) -> f64 {
639 plane[0] * point[0] + plane[1] * point[1] + plane[2] * point[2] - plane[3]
640}
641#[allow(dead_code)]
643pub fn bezier_arc_length(p0: &Vec3, p1: &Vec3, p2: &Vec3, p3: &Vec3, n: usize) -> Real {
644 let mut len = 0.0;
645 let mut prev = *p0;
646 for i in 1..=n {
647 let t = i as Real / n as Real;
648 let pt = bezier_cubic(p0, p1, p2, p3, t);
649 len += (pt - prev).norm();
650 prev = pt;
651 }
652 len
653}
654#[allow(dead_code)]
656pub fn vec3_cross(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
657 [
658 a[1] * b[2] - a[2] * b[1],
659 a[2] * b[0] - a[0] * b[2],
660 a[0] * b[1] - a[1] * b[0],
661 ]
662}
663#[allow(dead_code)]
667pub fn vec3_triple_product(a: [f64; 3], b: [f64; 3], c: [f64; 3]) -> f64 {
668 let bc = vec3_cross(b, c);
669 a[0] * bc[0] + a[1] * bc[1] + a[2] * bc[2]
670}
671#[allow(dead_code)]
675pub fn vec3_rotate_by_angle(v: [f64; 3], axis: [f64; 3], angle: f64) -> [f64; 3] {
676 let cos_a = angle.cos();
677 let sin_a = angle.sin();
678 let dot = v[0] * axis[0] + v[1] * axis[1] + v[2] * axis[2];
679 let cross = vec3_cross(axis, v);
680 [
681 cos_a * v[0] + sin_a * cross[0] + (1.0 - cos_a) * dot * axis[0],
682 cos_a * v[1] + sin_a * cross[1] + (1.0 - cos_a) * dot * axis[1],
683 cos_a * v[2] + sin_a * cross[2] + (1.0 - cos_a) * dot * axis[2],
684 ]
685}
686#[allow(dead_code)]
690pub fn mat3_adjugate(m: [[f64; 3]; 3]) -> [[f64; 3]; 3] {
691 let c = |r: usize, c_idx: usize| -> f64 {
692 let rows: Vec<usize> = (0..3).filter(|&x| x != r).collect();
693 let cols: Vec<usize> = (0..3).filter(|&x| x != c_idx).collect();
694 let det2 =
695 m[rows[0]][cols[0]] * m[rows[1]][cols[1]] - m[rows[0]][cols[1]] * m[rows[1]][cols[0]];
696 let sign = if (r + c_idx).is_multiple_of(2) {
697 1.0
698 } else {
699 -1.0
700 };
701 sign * det2
702 };
703 [
704 [c(0, 0), c(1, 0), c(2, 0)],
705 [c(0, 1), c(1, 1), c(2, 1)],
706 [c(0, 2), c(1, 2), c(2, 2)],
707 ]
708}
709#[allow(dead_code)]
714pub fn mat3_cayley_hamilton(m: [[f64; 3]; 3]) -> [[f64; 3]; 3] {
715 let i3 = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
716 let tr_m = m[0][0] + m[1][1] + m[2][2];
717 let m2 = mat3_mul_mat3(m, m);
718 let m3 = mat3_mul_mat3(m2, m);
719 let tr_m2 = m2[0][0] + m2[1][1] + m2[2][2];
720 let c2 = (tr_m * tr_m - tr_m2) * 0.5;
721 let det = mat3_det(m);
722 let mut result = [[0.0_f64; 3]; 3];
723 for i in 0..3 {
724 for j in 0..3 {
725 result[i][j] = m3[i][j] - tr_m * m2[i][j] + c2 * m[i][j] - det * i3[i][j];
726 }
727 }
728 result
729}
730#[allow(dead_code)]
735pub fn quat_arr_log(q: [f64; 4]) -> [f64; 4] {
736 let [x, y, z, w] = q;
737 let w_clamped = w.clamp(-1.0, 1.0);
738 let theta = w_clamped.acos();
739 let sin_theta = theta.sin();
740 if sin_theta.abs() < 1e-10 {
741 return [0.0, 0.0, 0.0, 0.0];
742 }
743 let s = theta / sin_theta;
744 [x * s, y * s, z * s, 0.0]
745}
746#[allow(dead_code)]
750pub fn quat_arr_exp(v: [f64; 4]) -> [f64; 4] {
751 let [x, y, z, _] = v;
752 let theta = (x * x + y * y + z * z).sqrt();
753 if theta < 1e-15 {
754 return [0.0, 0.0, 0.0, 1.0];
755 }
756 let s = theta.sin() / theta;
757 [x * s, y * s, z * s, theta.cos()]
758}
759#[allow(dead_code)]
763pub fn quat_to_axis_angle(q: [f64; 4]) -> ([f64; 3], f64) {
764 let [x, y, z, w] = q;
765 let w_c = w.clamp(-1.0, 1.0);
766 let angle = 2.0 * w_c.acos();
767 let sin_half = (1.0 - w_c * w_c).sqrt();
768 if sin_half < 1e-10 {
769 return ([0.0, 1.0, 0.0], 0.0);
770 }
771 ([x / sin_half, y / sin_half, z / sin_half], angle)
772}
773#[allow(dead_code)]
778pub fn quat_squad(q1: [f64; 4], q2: [f64; 4], s1: [f64; 4], s2: [f64; 4], t: f64) -> [f64; 4] {
779 let slerp_q = quat_arr_slerp(q1, q2, t);
780 let slerp_s = quat_arr_slerp(s1, s2, t);
781 quat_arr_slerp(slerp_q, slerp_s, 2.0 * t * (1.0 - t))
782}
783#[cfg(test)]
784mod proptest_tests {
785
786 use crate::Vec3;
787
788 use crate::math::types::{Plane, Ray};
789
790 use proptest::prelude::*;
791 fn coord() -> impl Strategy<Value = f64> {
792 -1e3_f64..1e3_f64
793 }
794 fn pos_coord() -> impl Strategy<Value = f64> {
795 0.01_f64..100.0_f64
796 }
797 fn vec3() -> impl Strategy<Value = Vec3> {
798 (coord(), coord(), coord()).prop_map(|(x, y, z)| Vec3::new(x, y, z))
799 }
800 fn nonzero_vec3() -> impl Strategy<Value = Vec3> {
801 (coord(), coord(), coord())
802 .prop_filter("norm must be nonzero", |(x, y, z)| {
803 (x * x + y * y + z * z).sqrt() > 1e-6
804 })
805 .prop_map(|(x, y, z)| Vec3::new(x, y, z))
806 }
807 proptest! {
808 #[test] fn prop_vec3_dot_commutative(a in vec3(), b in vec3()) { let ab = a.dot(&
809 b); let ba = b.dot(& a); prop_assert!((ab - ba).abs() < 1e-10,
810 "dot not commutative: {} vs {}", ab, ba); } #[test] fn
811 prop_vec3_cross_anti_commutative(a in vec3(), b in vec3()) { let ab = a.cross(&
812 b); let ba = b.cross(& a); let diff = (ab + ba).norm(); prop_assert!(diff < 1e-6,
813 "cross not anti-commutative: diff norm={}", diff); } #[test] fn
814 prop_vec3_triangle_inequality(a in vec3(), b in vec3()) { let sum_norm = (a + b)
815 .norm(); let norm_sum = a.norm() + b.norm(); prop_assert!(sum_norm <= norm_sum +
816 1e-10, "triangle inequality violated: |a+b|={} > |a|+|b|={}", sum_norm,
817 norm_sum); } #[test] fn prop_vec3_normalize_unit(v in nonzero_vec3()) { let n = v
818 .normalize(); let len = n.norm(); prop_assert!((len - 1.0).abs() < 1e-10,
819 "normalized vector has norm {} != 1", len); } #[test] fn
820 prop_ray_point_at_parametric(ox in coord(), oy in coord(), oz in coord(), dx in
821 coord(), dy in coord(), dz in coord(), t in 0.0_f64..10.0_f64,) { let origin =
822 Vec3::new(ox, oy, oz); let direction = Vec3::new(dx, dy, dz); let ray =
823 Ray::new(origin, direction); let p = ray.point_at(t); let expected = origin +
824 direction * t; let diff = (p - expected).norm(); prop_assert!(diff < 1e-10,
825 "ray.point_at mismatch: diff={}", diff); } #[test] fn
826 prop_plane_signed_distance_positive_side(nx in pos_coord(), ny in pos_coord(), nz
827 in pos_coord(), d in coord(), s in 0.01_f64..10.0_f64,) { let n = Vec3::new(nx,
828 ny, nz).normalize(); let plane = Plane::new(n, d); let point = n * (d + s); let
829 dist = plane.signed_distance(& point); prop_assert!((dist - s).abs() < 1e-6,
830 "expected signed distance {}, got {}", s, dist); }
831 }
832}
833#[cfg(test)]
834mod tests {
835 use super::*;
836 use crate::Mat3;
837 use crate::Vec3;
838 use crate::differential_geometry::mat3_det;
839 use crate::differential_geometry::mat3_mul_vec3;
840 use crate::differential_geometry::mat3_transpose;
841 use crate::math::Vec4;
842 use crate::math::bezier_arc_length;
843 use crate::math::bezier_cubic;
844 use crate::math::bspline_basis3;
845 use crate::math::dual_differentiate;
846 use crate::math::hermite_interpolate;
847 use crate::math::look_at;
848 use crate::math::mat3_adjugate;
849 use crate::math::mat3_arr_inverse;
850 use crate::math::mat3_cayley_hamilton;
851 use crate::math::mat3_determinant;
852 use crate::math::mat3_eigenvalues_symmetric;
853 use crate::math::mat3_inverse;
854 use crate::math::mat3_mul_mat3;
855 use crate::math::mat3_outer_product;
856 use crate::math::orthographic;
857 use crate::math::perspective;
858 use crate::math::plane_from_point_normal;
859 use crate::math::plane_plane_intersection;
860 use crate::math::plane_signed_dist;
861 use crate::math::polar_decomposition;
862 use crate::math::quat_arr_exp;
863 use crate::math::quat_arr_from_axis_angle;
864 use crate::math::quat_arr_log;
865 use crate::math::quat_arr_slerp;
866 use crate::math::quat_from_axis_angle;
867 use crate::math::quat_from_euler;
868 use crate::math::quat_multiply;
869 use crate::math::quat_slerp;
870 use crate::math::quat_to_axis_angle;
871 use crate::math::quat_to_euler;
872 use crate::math::quat_to_mat3;
873 use crate::math::sh_project_l1;
874 use crate::math::sh_y00;
875 use crate::math::sh_y10;
876 use crate::math::skew_symmetric;
877 use crate::math::symmetric_eigen3;
878 use crate::math::three_plane_intersection;
879 use crate::math::types::{Aabb, Dual, Frustum, Plane, Ray};
880 use crate::math::vec3_cross;
881 use crate::math::vec3_lerp;
882 use crate::math::vec3_project;
883 use crate::math::vec3_reflect;
884 use crate::math::vec3_rotate_by_angle;
885 use crate::math::vec3_triple_product;
886 use crate::math::vec4_direction;
887 use crate::math::vec4_point;
888 use crate::math::vec4_to_vec3;
889 #[test]
890 fn test_ray_point_at() {
891 let ray = Ray::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(1.0, 0.0, 0.0));
892 let p = ray.point_at(3.0);
893 assert!((p.x - 3.0).abs() < 1e-10);
894 assert!(p.y.abs() < 1e-10);
895 }
896 #[test]
897 fn test_plane_signed_distance() {
898 let plane = Plane::new(Vec3::new(0.0, 1.0, 0.0), 0.0);
899 assert!((plane.signed_distance(&Vec3::new(0.0, 5.0, 0.0)) - 5.0).abs() < 1e-10);
900 assert!((plane.signed_distance(&Vec3::new(0.0, -3.0, 0.0)) + 3.0).abs() < 1e-10);
901 }
902 #[test]
903 fn test_ray_plane_intersection() {
904 let ray = Ray::new(Vec3::new(0.0, 1.0, 0.0), Vec3::new(0.0, -1.0, 0.0));
905 let plane = Plane::new(Vec3::new(0.0, 1.0, 0.0), 0.0);
906 let t = ray.intersect_plane(&plane).expect("should intersect");
907 assert!((t - 1.0).abs() < 1e-10, "expected t=1.0, got {}", t);
908 let hit = ray.point_at(t);
909 assert!(hit.y.abs() < 1e-10);
910 }
911 #[test]
912 fn test_ray_plane_miss() {
913 let ray = Ray::new(Vec3::new(0.0, 1.0, 0.0), Vec3::new(1.0, 0.0, 0.0));
914 let plane = Plane::new(Vec3::new(0.0, 1.0, 0.0), 0.0);
915 assert!(ray.intersect_plane(&plane).is_none());
916 }
917 #[test]
918 fn test_plane_project() {
919 let plane = Plane::new(Vec3::new(0.0, 1.0, 0.0), 0.0);
920 let point = Vec3::new(3.0, 5.0, 7.0);
921 let d = plane.signed_distance(&point);
922 let projected = point - plane.normal * d;
923 assert!(plane.signed_distance(&projected).abs() < 1e-10);
924 }
925 #[test]
926 fn test_quat_from_axis_angle_identity() {
927 let q = quat_from_axis_angle(&Vec3::new(0.0, 1.0, 0.0), 0.0);
928 let (roll, pitch, yaw) = quat_to_euler(&q);
929 assert!(roll.abs() < 1e-10);
930 assert!(pitch.abs() < 1e-10);
931 assert!(yaw.abs() < 1e-10);
932 }
933 #[test]
934 fn test_quat_from_axis_angle_90_degrees() {
935 let q = quat_from_axis_angle(&Vec3::new(0.0, 0.0, 1.0), std::f64::consts::FRAC_PI_2);
936 let v = q.transform_vector(&Vec3::new(1.0, 0.0, 0.0));
937 assert!((v.x).abs() < 1e-10, "x={}", v.x);
938 assert!((v.y - 1.0).abs() < 1e-10, "y={}", v.y);
939 assert!((v.z).abs() < 1e-10, "z={}", v.z);
940 }
941 #[test]
942 fn test_quat_euler_roundtrip() {
943 let roll = 0.3;
944 let pitch = 0.5;
945 let yaw = 0.7;
946 let q = quat_from_euler(roll, pitch, yaw);
947 let (r2, p2, y2) = quat_to_euler(&q);
948 assert!((r2 - roll).abs() < 1e-10, "roll: {} vs {}", r2, roll);
949 assert!((p2 - pitch).abs() < 1e-10, "pitch: {} vs {}", p2, pitch);
950 assert!((y2 - yaw).abs() < 1e-10, "yaw: {} vs {}", y2, yaw);
951 }
952 #[test]
953 fn test_quat_slerp_endpoints() {
954 let a = quat_from_axis_angle(&Vec3::new(0.0, 1.0, 0.0), 0.0);
955 let b = quat_from_axis_angle(&Vec3::new(0.0, 1.0, 0.0), 1.0);
956 let q0 = quat_slerp(&a, &b, 0.0);
957 let q1 = quat_slerp(&a, &b, 1.0);
958 assert!(q0.angle_to(&a) < 1e-10);
959 assert!(q1.angle_to(&b) < 1e-10);
960 }
961 #[test]
962 fn test_quat_slerp_midpoint() {
963 let a = quat_from_axis_angle(&Vec3::new(0.0, 1.0, 0.0), 0.0);
964 let b = quat_from_axis_angle(&Vec3::new(0.0, 1.0, 0.0), 1.0);
965 let mid = quat_slerp(&a, &b, 0.5);
966 let expected = quat_from_axis_angle(&Vec3::new(0.0, 1.0, 0.0), 0.5);
967 assert!(mid.angle_to(&expected) < 1e-10);
968 }
969 #[test]
970 fn test_mat3_determinant_identity() {
971 let m = Mat3::identity();
972 assert!((mat3_determinant(&m) - 1.0).abs() < 1e-10);
973 }
974 #[test]
975 fn test_mat3_determinant_scaled() {
976 let m = Mat3::identity() * 2.0;
977 assert!((mat3_determinant(&m) - 8.0).abs() < 1e-10);
978 }
979 #[test]
980 fn test_mat3_inverse_identity() {
981 let m = Mat3::identity();
982 let inv = mat3_inverse(&m).expect("identity should be invertible");
983 let diff = (inv - Mat3::identity()).norm();
984 assert!(diff < 1e-10);
985 }
986 #[test]
987 fn test_mat3_inverse_product_is_identity() {
988 let m = Mat3::new(1.0, 2.0, 3.0, 0.0, 1.0, 4.0, 5.0, 6.0, 0.0);
989 let inv = mat3_inverse(&m).expect("should be invertible");
990 let product = m * inv;
991 let diff = (product - Mat3::identity()).norm();
992 assert!(diff < 1e-8, "M * M^-1 should be identity, diff={}", diff);
993 }
994 #[test]
995 fn test_mat3_singular_no_inverse() {
996 let m = Mat3::new(1.0, 2.0, 3.0, 2.0, 4.0, 6.0, 1.0, 1.0, 1.0);
997 assert!(mat3_inverse(&m).is_none());
998 }
999 #[test]
1000 fn test_mat3_eigenvalues_diagonal() {
1001 let m = Mat3::new(3.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 2.0);
1002 let eigs = mat3_eigenvalues_symmetric(&m);
1003 assert!((eigs[0] - 1.0).abs() < 1e-10);
1004 assert!((eigs[1] - 2.0).abs() < 1e-10);
1005 assert!((eigs[2] - 3.0).abs() < 1e-10);
1006 }
1007 #[test]
1008 fn test_mat3_trace() {
1009 let m = Mat3::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0);
1010 assert!((mat3_trace(&m) - 15.0).abs() < 1e-10);
1011 }
1012 #[test]
1013 fn test_mat3_frobenius_norm_identity() {
1014 let m = Mat3::identity();
1015 assert!((mat3_frobenius_norm(&m) - 3.0_f64.sqrt()).abs() < 1e-10);
1016 }
1017 #[test]
1018 fn test_skew_symmetric_cross_product() {
1019 let a = Vec3::new(1.0, 2.0, 3.0);
1020 let b = Vec3::new(4.0, 5.0, 6.0);
1021 let cross_direct = a.cross(&b);
1022 let cross_matrix = skew_symmetric(&a) * b;
1023 let diff = (cross_direct - cross_matrix).norm();
1024 assert!(
1025 diff < 1e-10,
1026 "skew_symmetric cross product mismatch: diff={}",
1027 diff
1028 );
1029 }
1030 #[test]
1031 fn test_perspective_nonzero() {
1032 let m = perspective(std::f64::consts::FRAC_PI_4, 16.0 / 9.0, 0.1, 100.0);
1033 assert!(m.norm() > 0.0);
1034 assert!((m[(3, 0)]).abs() < 1e-10);
1035 assert!((m[(3, 1)]).abs() < 1e-10);
1036 assert!((m[(3, 2)] + 1.0).abs() < 1e-10);
1037 assert!((m[(3, 3)]).abs() < 1e-10);
1038 }
1039 #[test]
1040 fn test_look_at_origin() {
1041 let eye = Vec3::new(0.0, 0.0, 5.0);
1042 let target = Vec3::new(0.0, 0.0, 0.0);
1043 let up = Vec3::new(0.0, 1.0, 0.0);
1044 let m = look_at(&eye, &target, &up);
1045 let p = m * vec4_point(&Vec3::zeros());
1046 let v3 = vec4_to_vec3(&p).unwrap();
1047 assert!((v3.x).abs() < 1e-8, "x={}", v3.x);
1048 assert!((v3.y).abs() < 1e-8, "y={}", v3.y);
1049 assert!((v3.z + 5.0).abs() < 1e-8, "z={}", v3.z);
1050 }
1051 #[test]
1052 fn test_orthographic_identity_like() {
1053 let m = orthographic(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);
1054 let p = m * vec4_point(&Vec3::zeros());
1055 let v3 = vec4_to_vec3(&p).unwrap();
1056 assert!(v3.norm() < 1e-8);
1057 }
1058 #[test]
1059 fn test_vec4_point_w_is_one() {
1060 let p = vec4_point(&Vec3::new(1.0, 2.0, 3.0));
1061 assert!((p.w - 1.0).abs() < 1e-10);
1062 }
1063 #[test]
1064 fn test_vec4_direction_w_is_zero() {
1065 let d = vec4_direction(&Vec3::new(1.0, 0.0, 0.0));
1066 assert!(d.w.abs() < 1e-10);
1067 }
1068 #[test]
1069 fn test_vec4_to_vec3_perspective_divide() {
1070 let v = Vec4::new(4.0, 6.0, 8.0, 2.0);
1071 let v3 = vec4_to_vec3(&v).unwrap();
1072 assert!((v3.x - 2.0).abs() < 1e-10);
1073 assert!((v3.y - 3.0).abs() < 1e-10);
1074 assert!((v3.z - 4.0).abs() < 1e-10);
1075 }
1076 #[test]
1077 fn test_vec4_to_vec3_w_zero_returns_none() {
1078 let v = Vec4::new(1.0, 2.0, 3.0, 0.0);
1079 assert!(vec4_to_vec3(&v).is_none());
1080 }
1081 #[test]
1082 fn test_plane_from_points() {
1083 let a = Vec3::new(0.0, 0.0, 0.0);
1084 let b = Vec3::new(1.0, 0.0, 0.0);
1085 let c = Vec3::new(0.0, 1.0, 0.0);
1086 let plane = Plane::from_points(&a, &b, &c).unwrap();
1087 assert!((plane.normal.z.abs() - 1.0).abs() < 1e-10);
1088 assert!(plane.signed_distance(&a).abs() < 1e-10);
1089 assert!(plane.signed_distance(&b).abs() < 1e-10);
1090 assert!(plane.signed_distance(&c).abs() < 1e-10);
1091 }
1092 #[test]
1093 fn test_plane_from_collinear_returns_none() {
1094 let a = Vec3::new(0.0, 0.0, 0.0);
1095 let b = Vec3::new(1.0, 0.0, 0.0);
1096 let c = Vec3::new(2.0, 0.0, 0.0);
1097 assert!(Plane::from_points(&a, &b, &c).is_none());
1098 }
1099 #[test]
1100 fn test_plane_project_point() {
1101 let plane = Plane::new(Vec3::new(0.0, 1.0, 0.0), 0.0);
1102 let point = Vec3::new(3.0, 5.0, 7.0);
1103 let projected = plane.project_point(&point);
1104 assert!(plane.signed_distance(&projected).abs() < 1e-10);
1105 assert!((projected.x - 3.0).abs() < 1e-10);
1106 assert!((projected.z - 7.0).abs() < 1e-10);
1107 }
1108 #[test]
1109 fn test_plane_reflect_point() {
1110 let plane = Plane::new(Vec3::new(0.0, 1.0, 0.0), 0.0);
1111 let point = Vec3::new(3.0, 5.0, 7.0);
1112 let reflected = plane.reflect_point(&point);
1113 assert!((reflected.x - 3.0).abs() < 1e-10);
1114 assert!((reflected.y + 5.0).abs() < 1e-10);
1115 assert!((reflected.z - 7.0).abs() < 1e-10);
1116 }
1117 #[test]
1118 fn test_plane_classify_point() {
1119 let plane = Plane::new(Vec3::new(0.0, 1.0, 0.0), 0.0);
1120 assert_eq!(plane.classify_point(&Vec3::new(0.0, 1.0, 0.0), 1e-6), 1);
1121 assert_eq!(plane.classify_point(&Vec3::new(0.0, -1.0, 0.0), 1e-6), -1);
1122 assert_eq!(plane.classify_point(&Vec3::new(0.0, 0.0, 0.0), 1e-6), 0);
1123 }
1124 #[test]
1125 fn test_plane_plane_intersection() {
1126 let a = Plane::new(Vec3::new(0.0, 1.0, 0.0), 0.0);
1127 let b = Plane::new(Vec3::new(1.0, 0.0, 0.0), 0.0);
1128 let (point, dir) = plane_plane_intersection(&a, &b).unwrap();
1129 assert!(a.signed_distance(&point).abs() < 1e-8);
1130 assert!(b.signed_distance(&point).abs() < 1e-8);
1131 assert!((dir.z.abs() - 1.0).abs() < 1e-8, "dir={:?}", dir);
1132 }
1133 #[test]
1134 fn test_plane_plane_parallel_returns_none() {
1135 let a = Plane::new(Vec3::new(0.0, 1.0, 0.0), 0.0);
1136 let b = Plane::new(Vec3::new(0.0, 1.0, 0.0), 5.0);
1137 assert!(plane_plane_intersection(&a, &b).is_none());
1138 }
1139 #[test]
1140 fn test_three_plane_intersection() {
1141 let a = Plane::new(Vec3::new(1.0, 0.0, 0.0), 1.0);
1142 let b = Plane::new(Vec3::new(0.0, 1.0, 0.0), 2.0);
1143 let c = Plane::new(Vec3::new(0.0, 0.0, 1.0), 3.0);
1144 let p = three_plane_intersection(&a, &b, &c).unwrap();
1145 assert!((p.x - 1.0).abs() < 1e-8);
1146 assert!((p.y - 2.0).abs() < 1e-8);
1147 assert!((p.z - 3.0).abs() < 1e-8);
1148 }
1149 #[test]
1150 fn test_polar_decomposition_identity() {
1151 let m = Mat3::identity();
1152 let (r, s) = polar_decomposition(&m, 100).expect("identity is invertible");
1153 let diff_r = (r - Mat3::identity()).norm();
1154 let diff_s = (s - Mat3::identity()).norm();
1155 assert!(diff_r < 1e-8, "R should be identity: diff={}", diff_r);
1156 assert!(diff_s < 1e-8, "S should be identity: diff={}", diff_s);
1157 }
1158 #[test]
1159 fn test_polar_decomposition_rotation_only() {
1160 let q = quat_from_axis_angle(&Vec3::new(0.0, 0.0, 1.0), std::f64::consts::FRAC_PI_4);
1161 let m = *q.to_rotation_matrix().matrix();
1162 let (r, s) = polar_decomposition(&m, 100).expect("rotation is invertible");
1163 let product = r * s;
1164 let diff = (product - m).norm();
1165 assert!(diff < 1e-6, "R*S should equal M: diff={}", diff);
1166 let s_diff = (s - Mat3::identity()).norm();
1167 assert!(s_diff < 1e-6, "S should be near identity: diff={}", s_diff);
1168 }
1169 #[test]
1170 fn test_polar_decomposition_product_equals_m() {
1171 let m = Mat3::new(1.0, 0.5, 0.0, 0.0, 1.0, 0.3, 0.0, 0.0, 1.0);
1172 let (r, s) = polar_decomposition(&m, 100).expect("M should be invertible");
1173 let product = r * s;
1174 let diff = (product - m).norm();
1175 assert!(diff < 1e-6, "R*S should equal M: diff={}", diff);
1176 }
1177 #[test]
1178 fn test_symmetric_eigen3_diagonal() {
1179 let m = Mat3::new(3.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 2.0);
1180 let (eigs, _) = symmetric_eigen3(&m);
1181 let mut sorted = eigs;
1182 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
1183 assert!((sorted[0] - 1.0).abs() < 1e-8, "eig0={}", sorted[0]);
1184 assert!((sorted[1] - 2.0).abs() < 1e-8, "eig1={}", sorted[1]);
1185 assert!((sorted[2] - 3.0).abs() < 1e-8, "eig2={}", sorted[2]);
1186 }
1187 #[test]
1188 fn test_symmetric_eigen3_eigenvectors_orthonormal() {
1189 let m = Mat3::new(4.0, 1.0, 0.0, 1.0, 3.0, 0.0, 0.0, 0.0, 2.0);
1190 let (_, v) = symmetric_eigen3(&m);
1191 for i in 0..3 {
1192 let col_i = v.column(i);
1193 let norm = col_i.norm();
1194 assert!((norm - 1.0).abs() < 1e-6, "col {} norm={}", i, norm);
1195 for j in (i + 1)..3 {
1196 let col_j = v.column(j);
1197 let dot = col_i.dot(&col_j);
1198 assert!(
1199 dot.abs() < 1e-6,
1200 "cols {} {} not orthogonal: dot={}",
1201 i,
1202 j,
1203 dot
1204 );
1205 }
1206 }
1207 }
1208 #[test]
1209 fn test_symmetric_eigen3_reconstruction() {
1210 let m = Mat3::new(2.0, 1.0, 0.0, 1.0, 3.0, 0.0, 0.0, 0.0, 5.0);
1211 let (eigs, v) = symmetric_eigen3(&m);
1212 let diag = Mat3::from_diagonal(&nalgebra::Vector3::new(eigs[0], eigs[1], eigs[2]));
1213 let reconstructed = v * diag * v.transpose();
1214 let diff = (reconstructed - m).norm();
1215 assert!(diff < 1e-6, "reconstruction diff={}", diff);
1216 }
1217 #[test]
1218 fn test_sh_y00_constant() {
1219 let expected = 0.5 / std::f64::consts::PI.sqrt();
1220 assert!((sh_y00() - expected).abs() < 1e-12);
1221 }
1222 #[test]
1223 fn test_sh_y1_orthogonal_to_constant() {
1224 let n = 1000;
1225 let golden = (1.0 + 5.0_f64.sqrt()) / 2.0;
1226 let mut sum_y10 = 0.0_f64;
1227 for i in 0..n {
1228 let theta = (1.0 - 2.0 * (i as f64 + 0.5) / n as f64).acos();
1229 let phi = 2.0 * std::f64::consts::PI * ((i as f64) / golden).fract();
1230 let dir = Vec3::new(
1231 theta.sin() * phi.cos(),
1232 theta.sin() * phi.sin(),
1233 theta.cos(),
1234 );
1235 sum_y10 += sh_y10(&dir);
1236 }
1237 let avg = sum_y10 / n as f64;
1238 assert!(avg.abs() < 0.01, "Y_1^0 average should be ~0, got {}", avg);
1239 }
1240 #[test]
1241 fn test_sh_project_l1_uniform_radiance() {
1242 let coeffs = sh_project_l1(|_| 1.0, 1000);
1243 let expected_c00 = 2.0 * std::f64::consts::PI.sqrt();
1244 assert!((coeffs[0] - expected_c00).abs() < 0.1, "c00={}", coeffs[0]);
1245 for &c in &coeffs[1..] {
1246 assert!(c.abs() < 0.1, "L1 coeff should be ~0 for uniform: {}", c);
1247 }
1248 }
1249 #[test]
1250 fn test_dual_add() {
1251 let a = Dual::new(3.0, 1.0);
1252 let b = Dual::new(2.0, 0.5);
1253 let c = a + b;
1254 assert!((c.re - 5.0).abs() < 1e-12);
1255 assert!((c.du - 1.5).abs() < 1e-12);
1256 }
1257 #[test]
1258 fn test_dual_mul() {
1259 let x = Dual::variable(3.0);
1260 let y = x * x;
1261 assert!((y.re - 9.0).abs() < 1e-12);
1262 assert!((y.du - 6.0).abs() < 1e-12);
1263 }
1264 #[test]
1265 fn test_dual_differentiate_polynomial() {
1266 let deriv = dual_differentiate(|x| x.powi(3) - Dual::constant(2.0) * x, 2.0);
1267 assert!((deriv - 10.0).abs() < 1e-10, "deriv={}", deriv);
1268 }
1269 #[test]
1270 fn test_dual_sin_derivative() {
1271 let x = std::f64::consts::FRAC_PI_4;
1272 let deriv = dual_differentiate(|d| d.sin(), x);
1273 assert!((deriv - x.cos()).abs() < 1e-12, "deriv={}", deriv);
1274 }
1275 #[test]
1276 fn test_dual_exp_derivative() {
1277 let deriv = dual_differentiate(|d| d.exp(), 1.0);
1278 assert!(
1279 (deriv - std::f64::consts::E).abs() < 1e-10,
1280 "deriv={}",
1281 deriv
1282 );
1283 }
1284 #[test]
1285 fn test_dual_sqrt_derivative() {
1286 let deriv = dual_differentiate(|d| d.sqrt(), 4.0);
1287 assert!((deriv - 0.25).abs() < 1e-10, "deriv={}", deriv);
1288 }
1289 #[test]
1290 fn test_hermite_interpolate_endpoints() {
1291 let p0 = Vec3::new(0.0, 0.0, 0.0);
1292 let p1 = Vec3::new(1.0, 0.0, 0.0);
1293 let m0 = Vec3::new(0.0, 1.0, 0.0);
1294 let m1 = Vec3::new(0.0, 1.0, 0.0);
1295 let at_0 = hermite_interpolate(&p0, &m0, &p1, &m1, 0.0);
1296 let at_1 = hermite_interpolate(&p0, &m0, &p1, &m1, 1.0);
1297 assert!((at_0 - p0).norm() < 1e-12, "t=0 should be p0");
1298 assert!((at_1 - p1).norm() < 1e-12, "t=1 should be p1");
1299 }
1300 #[test]
1301 fn test_bezier_cubic_endpoints() {
1302 let p0 = Vec3::new(0.0, 0.0, 0.0);
1303 let p1 = Vec3::new(1.0, 2.0, 0.0);
1304 let p2 = Vec3::new(2.0, 2.0, 0.0);
1305 let p3 = Vec3::new(3.0, 0.0, 0.0);
1306 let at_0 = bezier_cubic(&p0, &p1, &p2, &p3, 0.0);
1307 let at_1 = bezier_cubic(&p0, &p1, &p2, &p3, 1.0);
1308 assert!((at_0 - p0).norm() < 1e-12);
1309 assert!((at_1 - p3).norm() < 1e-12);
1310 }
1311 #[test]
1312 fn test_catmull_rom_interpolates_endpoints() {
1313 let p0 = Vec3::new(-1.0, 0.0, 0.0);
1314 let p1 = Vec3::new(0.0, 0.0, 0.0);
1315 let p2 = Vec3::new(1.0, 0.0, 0.0);
1316 let p3 = Vec3::new(2.0, 0.0, 0.0);
1317 let at_0 = catmull_rom(&p0, &p1, &p2, &p3, 0.0);
1318 let at_1 = catmull_rom(&p0, &p1, &p2, &p3, 1.0);
1319 assert!((at_0 - p1).norm() < 1e-12);
1320 assert!((at_1 - p2).norm() < 1e-12);
1321 }
1322 #[test]
1323 fn test_bspline_basis3_partition_of_unity() {
1324 for i in 0..=10 {
1325 let t = i as f64 / 10.0;
1326 let b = bspline_basis3(t);
1327 let sum: f64 = b.iter().sum();
1328 assert!((sum - 1.0).abs() < 1e-12, "t={}: sum={}", t, sum);
1329 }
1330 }
1331 #[test]
1332 fn test_bezier_arc_length_straight_line() {
1333 let p0 = Vec3::new(0.0, 0.0, 0.0);
1334 let p1 = Vec3::new(1.0, 0.0, 0.0);
1335 let p2 = Vec3::new(2.0, 0.0, 0.0);
1336 let p3 = Vec3::new(3.0, 0.0, 0.0);
1337 let len = bezier_arc_length(&p0, &p1, &p2, &p3, 100);
1338 assert!((len - 3.0).abs() < 1e-6, "arc length={}", len);
1339 }
1340 #[test]
1341 fn test_frustum_contains_origin() {
1342 let vp = perspective(std::f64::consts::FRAC_PI_4, 1.0, 0.1, 100.0);
1343 let frustum = Frustum::from_view_projection(&vp);
1344 assert_eq!(frustum.planes.len(), 6);
1345 for plane in &frustum.planes {
1346 assert!((plane.normal.norm() - 1.0).abs() < 1e-6);
1347 }
1348 }
1349 #[test]
1350 fn test_frustum_sphere_outside() {
1351 let vp = perspective(std::f64::consts::FRAC_PI_4, 1.0, 0.1, 100.0);
1352 let frustum = Frustum::from_view_projection(&vp);
1353 let result = frustum.intersects_sphere(&Vec3::new(1000.0, 0.0, 0.0), 1.0);
1354 let _ = result;
1355 }
1356 #[test]
1357 fn test_mat3_transpose_roundtrip() {
1358 let m = [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]];
1359 let t = mat3_transpose(m);
1360 let tt = mat3_transpose(t);
1361 for i in 0..3 {
1362 for j in 0..3 {
1363 assert!((m[i][j] - tt[i][j]).abs() < 1e-12);
1364 }
1365 }
1366 }
1367 #[test]
1368 fn test_mat3_mul_vec3_identity() {
1369 let identity = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
1370 let v = [3.0, 4.0, 5.0];
1371 let result = mat3_mul_vec3(identity, v);
1372 for i in 0..3 {
1373 assert!((result[i] - v[i]).abs() < 1e-12);
1374 }
1375 }
1376 #[test]
1377 fn test_mat3_arr_inverse() {
1378 let m = [[1.0, 2.0, 0.0], [0.0, 1.0, 3.0], [0.0, 0.0, 1.0]];
1379 let inv = mat3_arr_inverse(m).expect("should be invertible");
1380 let prod = mat3_mul_mat3(m, inv);
1381 for i in 0..3 {
1382 for j in 0..3 {
1383 let expected = if i == j { 1.0 } else { 0.0 };
1384 assert!(
1385 (prod[i][j] - expected).abs() < 1e-10,
1386 "prod[{i}][{j}]={}",
1387 prod[i][j]
1388 );
1389 }
1390 }
1391 }
1392 #[test]
1393 fn test_mat3_outer_product() {
1394 let a = [1.0, 0.0, 0.0];
1395 let b = [0.0, 1.0, 0.0];
1396 let op = mat3_outer_product(a, b);
1397 assert!((op[0][1] - 1.0).abs() < 1e-12);
1398 assert!(op[0][0].abs() < 1e-12);
1399 assert!(op[1][1].abs() < 1e-12);
1400 }
1401 #[test]
1402 fn test_quat_arr_from_axis_angle_to_mat3_roundtrip() {
1403 let q = quat_arr_from_axis_angle([0.0, 0.0, 1.0], std::f64::consts::FRAC_PI_2);
1404 let m = quat_to_mat3(q);
1405 let v = mat3_mul_vec3(m, [1.0, 0.0, 0.0]);
1406 assert!(v[0].abs() < 1e-10, "x={}", v[0]);
1407 assert!((v[1] - 1.0).abs() < 1e-10, "y={}", v[1]);
1408 assert!(v[2].abs() < 1e-10, "z={}", v[2]);
1409 }
1410 #[test]
1411 fn test_quat_multiply_identity() {
1412 let id = [0.0, 0.0, 0.0, 1.0];
1413 let q = quat_arr_from_axis_angle([0.0, 1.0, 0.0], 0.5);
1414 let result = quat_multiply(id, q);
1415 for i in 0..4 {
1416 assert!((result[i] - q[i]).abs() < 1e-12, "component {i}");
1417 }
1418 }
1419 #[test]
1420 fn test_quat_arr_slerp_at_t0_and_t1() {
1421 let p = quat_arr_from_axis_angle([0.0, 1.0, 0.0], 0.0);
1422 let q = quat_arr_from_axis_angle([0.0, 1.0, 0.0], 1.0);
1423 let s0 = quat_arr_slerp(p, q, 0.0);
1424 let s1 = quat_arr_slerp(p, q, 1.0);
1425 for i in 0..4 {
1426 assert!(
1427 (s0[i] - p[i]).abs() < 1e-10,
1428 "t=0 component {i}: got {}, expected {}",
1429 s0[i],
1430 p[i]
1431 );
1432 assert!(
1433 (s1[i] - q[i]).abs() < 1e-10,
1434 "t=1 component {i}: got {}, expected {}",
1435 s1[i],
1436 q[i]
1437 );
1438 }
1439 }
1440 #[test]
1441 fn test_vec3_reflect() {
1442 let v = [1.0, -1.0, 0.0];
1443 let n = [0.0, 1.0, 0.0];
1444 let r = vec3_reflect(v, n);
1445 assert!((r[0] - 1.0).abs() < 1e-12);
1446 assert!((r[1] - 1.0).abs() < 1e-12);
1447 assert!(r[2].abs() < 1e-12);
1448 }
1449 #[test]
1450 fn test_vec3_project() {
1451 let a = [3.0, 4.0, 0.0];
1452 let onto = [1.0, 0.0, 0.0];
1453 let proj = vec3_project(a, onto);
1454 assert!((proj[0] - 3.0).abs() < 1e-12);
1455 assert!(proj[1].abs() < 1e-12);
1456 assert!(proj[2].abs() < 1e-12);
1457 }
1458 #[test]
1459 fn test_vec3_lerp() {
1460 let a = [0.0, 0.0, 0.0];
1461 let b = [2.0, 4.0, 6.0];
1462 let mid = vec3_lerp(a, b, 0.5);
1463 assert!((mid[0] - 1.0).abs() < 1e-12);
1464 assert!((mid[1] - 2.0).abs() < 1e-12);
1465 assert!((mid[2] - 3.0).abs() < 1e-12);
1466 }
1467 #[test]
1468 fn test_plane_from_point_normal_and_signed_dist() {
1469 let p = [0.0, 5.0, 0.0];
1470 let n = [0.0, 1.0, 0.0];
1471 let plane = plane_from_point_normal(p, n);
1472 let dist_above = plane_signed_dist(plane, [0.0, 8.0, 0.0]);
1473 let dist_below = plane_signed_dist(plane, [0.0, 3.0, 0.0]);
1474 assert!((dist_above - 3.0).abs() < 1e-12);
1475 assert!((dist_below + 2.0).abs() < 1e-12);
1476 }
1477 #[test]
1478 fn test_vec3_cross() {
1479 let a = [1.0_f64, 0.0, 0.0];
1480 let b = [0.0_f64, 1.0, 0.0];
1481 let c = vec3_cross(a, b);
1482 assert!(c[0].abs() < 1e-12);
1483 assert!(c[1].abs() < 1e-12);
1484 assert!((c[2] - 1.0).abs() < 1e-12);
1485 }
1486 #[test]
1487 fn test_vec3_triple_product() {
1488 let a = [1.0_f64, 0.0, 0.0];
1489 let b = [0.0_f64, 1.0, 0.0];
1490 let c = [0.0_f64, 0.0, 1.0];
1491 assert!((vec3_triple_product(a, b, c) - 1.0).abs() < 1e-12);
1492 }
1493 #[test]
1494 fn test_vec3_rotate_by_angle_around_z() {
1495 let v = [1.0_f64, 0.0, 0.0];
1496 let axis = [0.0_f64, 0.0, 1.0];
1497 let r = vec3_rotate_by_angle(v, axis, std::f64::consts::FRAC_PI_2);
1498 assert!(r[0].abs() < 1e-12, "x={}", r[0]);
1499 assert!((r[1] - 1.0).abs() < 1e-12, "y={}", r[1]);
1500 assert!(r[2].abs() < 1e-12, "z={}", r[2]);
1501 }
1502 #[test]
1503 fn test_vec3_rotate_zero_angle() {
1504 let v = [3.0_f64, 4.0, 5.0];
1505 let axis = [0.0_f64, 1.0, 0.0];
1506 let r = vec3_rotate_by_angle(v, axis, 0.0);
1507 for i in 0..3 {
1508 assert!((r[i] - v[i]).abs() < 1e-12, "component {i}");
1509 }
1510 }
1511 #[test]
1512 fn test_mat3_adjugate_identity() {
1513 let m = [[1.0_f64, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
1514 let adj = mat3_adjugate(m);
1515 for i in 0..3 {
1516 for j in 0..3 {
1517 let exp = if i == j { 1.0 } else { 0.0 };
1518 assert!(
1519 (adj[i][j] - exp).abs() < 1e-12,
1520 "adj[{i}][{j}]={}",
1521 adj[i][j]
1522 );
1523 }
1524 }
1525 }
1526 #[test]
1527 fn test_mat3_adjugate_relation() {
1528 let m = [[1.0_f64, 2.0, 0.0], [0.0, 1.0, 3.0], [0.0, 0.0, 1.0]];
1529 let adj = mat3_adjugate(m);
1530 let det = mat3_det(m);
1531 let prod = mat3_mul_mat3(m, adj);
1532 for i in 0..3 {
1533 for j in 0..3 {
1534 let exp = if i == j { det } else { 0.0 };
1535 assert!(
1536 (prod[i][j] - exp).abs() < 1e-10,
1537 "prod[{i}][{j}]={} exp={}",
1538 prod[i][j],
1539 exp
1540 );
1541 }
1542 }
1543 }
1544 #[test]
1545 fn test_cayley_hamilton() {
1546 let m = [[1.0_f64, 2.0, 3.0], [0.0, 4.0, 5.0], [0.0, 0.0, 6.0]];
1547 let zero = mat3_cayley_hamilton(m);
1548 for i in 0..3 {
1549 for j in 0..3 {
1550 assert!(zero[i][j].abs() < 1e-8, "CH[{i}][{j}]={}", zero[i][j]);
1551 }
1552 }
1553 }
1554 #[test]
1555 fn test_quat_log_exp_roundtrip() {
1556 let q = quat_arr_from_axis_angle([0.0, 1.0, 0.0], 0.8);
1557 let log_q = quat_arr_log(q);
1558 let exp_log_q = quat_arr_exp(log_q);
1559 for i in 0..4 {
1560 assert!((exp_log_q[i] - q[i]).abs() < 1e-10, "component {i}");
1561 }
1562 }
1563 #[test]
1564 fn test_quat_log_identity() {
1565 let id = [0.0_f64, 0.0, 0.0, 1.0];
1566 let log_id = quat_arr_log(id);
1567 for &v in &log_id {
1568 assert!(v.abs() < 1e-12, "log(identity) should be zero: {}", v);
1569 }
1570 }
1571 #[test]
1572 fn test_quat_to_axis_angle_roundtrip() {
1573 let angle = 1.2_f64;
1574 let axis = [0.0_f64, 1.0, 0.0];
1575 let q = quat_arr_from_axis_angle(axis, angle);
1576 let (out_axis, out_angle) = quat_to_axis_angle(q);
1577 assert!(
1578 (out_angle - angle).abs() < 1e-10,
1579 "angle mismatch: {} vs {}",
1580 out_angle,
1581 angle
1582 );
1583 for i in 0..3 {
1584 assert!(
1585 (out_axis[i] - axis[i]).abs() < 1e-10,
1586 "axis[{i}]: {} vs {}",
1587 out_axis[i],
1588 axis[i]
1589 );
1590 }
1591 }
1592 #[test]
1593 fn test_aabb_contains() {
1594 let aabb = Aabb::new([0.0, 0.0, 0.0], [2.0, 2.0, 2.0]);
1595 assert!(aabb.contains([1.0, 1.0, 1.0]));
1596 assert!(!aabb.contains([3.0, 1.0, 1.0]));
1597 }
1598 #[test]
1599 fn test_aabb_union() {
1600 let a = Aabb::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
1601 let b = Aabb::new([-1.0, -1.0, -1.0], [0.5, 0.5, 0.5]);
1602 let u = a.union(&b);
1603 for i in 0..3 {
1604 assert!((u.min[i] - (-1.0)).abs() < 1e-12);
1605 }
1606 assert!((u.max[0] - 1.0).abs() < 1e-12);
1607 }
1608 #[test]
1609 fn test_aabb_intersect() {
1610 let a = Aabb::new([0.0, 0.0, 0.0], [2.0, 2.0, 2.0]);
1611 let b = Aabb::new([1.0, 1.0, 1.0], [3.0, 3.0, 3.0]);
1612 let inter = a.intersect(&b).expect("should overlap");
1613 for i in 0..3 {
1614 assert!((inter.min[i] - 1.0).abs() < 1e-12);
1615 assert!((inter.max[i] - 2.0).abs() < 1e-12);
1616 }
1617 }
1618 #[test]
1619 fn test_aabb_no_intersect() {
1620 let a = Aabb::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
1621 let b = Aabb::new([2.0, 2.0, 2.0], [3.0, 3.0, 3.0]);
1622 assert!(a.intersect(&b).is_none());
1623 }
1624 #[test]
1625 fn test_aabb_expand() {
1626 let a = Aabb::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
1627 let exp = a.expand(0.5);
1628 for i in 0..3 {
1629 assert!((exp.min[i] - (-0.5)).abs() < 1e-12);
1630 assert!((exp.max[i] - 1.5).abs() < 1e-12);
1631 }
1632 }
1633 #[test]
1634 fn test_aabb_intersect_ray() {
1635 let aabb = Aabb::new([0.0, 0.0, 0.0], [2.0, 2.0, 2.0]);
1636 let hit = aabb.intersect_ray([-1.0, 1.0, 1.0], [1.0, 0.0, 0.0]);
1637 assert!(hit.is_some(), "ray should hit AABB");
1638 let (t0, _t1) = hit.unwrap();
1639 assert!((t0 - 1.0).abs() < 1e-10, "t0={}", t0);
1640 }
1641 #[test]
1642 fn test_aabb_ray_miss() {
1643 let aabb = Aabb::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
1644 let hit = aabb.intersect_ray([0.0, 5.0, 0.5], [1.0, 0.0, 0.0]);
1645 assert!(hit.is_none(), "ray should miss AABB");
1646 }
1647}
1648#[allow(dead_code)]
1652pub fn vec3_outer_product(a: &Vec3, b: &Vec3) -> Mat3 {
1653 Mat3::new(
1654 a.x * b.x,
1655 a.x * b.y,
1656 a.x * b.z,
1657 a.y * b.x,
1658 a.y * b.y,
1659 a.y * b.z,
1660 a.z * b.x,
1661 a.z * b.y,
1662 a.z * b.z,
1663 )
1664}
1665#[allow(dead_code)]
1670pub fn cross_matrix(v: &Vec3) -> Mat3 {
1671 skew_symmetric(v)
1672}
1673#[allow(dead_code)]
1682pub fn gram_schmidt(v0: &Vec3, v1: &Vec3, v2: &Vec3) -> Option<(Vec3, Vec3, Vec3)> {
1683 let e0_len = v0.norm();
1684 if e0_len < 1e-12 {
1685 return None;
1686 }
1687 let e0 = v0 / e0_len;
1688 let u1 = v1 - e0 * e0.dot(v1);
1689 let u1_len = u1.norm();
1690 if u1_len < 1e-12 {
1691 return None;
1692 }
1693 let e1 = u1 / u1_len;
1694 let u2 = v2 - e0 * e0.dot(v2) - e1 * e1.dot(v2);
1695 let u2_len = u2.norm();
1696 if u2_len < 1e-12 {
1697 return None;
1698 }
1699 let e2 = u2 / u2_len;
1700 Some((e0, e1, e2))
1701}
1702#[allow(dead_code)]
1708pub fn cartesian_to_spherical(v: &Vec3) -> (Real, Real, Real) {
1709 let r = v.norm();
1710 if r < 1e-300 {
1711 return (0.0, 0.0, 0.0);
1712 }
1713 let theta = (v.z / r).clamp(-1.0, 1.0).acos();
1714 let phi = v.y.atan2(v.x);
1715 (r, theta, phi)
1716}
1717#[allow(dead_code)]
1723pub fn spherical_to_cartesian(r: Real, theta: Real, phi: Real) -> Vec3 {
1724 Vec3::new(
1725 r * theta.sin() * phi.cos(),
1726 r * theta.sin() * phi.sin(),
1727 r * theta.cos(),
1728 )
1729}
1730#[allow(dead_code)]
1736pub fn cartesian_to_cylindrical(v: &Vec3) -> (Real, Real, Real) {
1737 let rho = (v.x * v.x + v.y * v.y).sqrt();
1738 let phi = v.y.atan2(v.x);
1739 (rho, phi, v.z)
1740}
1741#[allow(dead_code)]
1743pub fn cylindrical_to_cartesian(rho: Real, phi: Real, z: Real) -> Vec3 {
1744 Vec3::new(rho * phi.cos(), rho * phi.sin(), z)
1745}
1746#[allow(dead_code)]
1751pub fn rodrigues_rotate(v: &Vec3, axis: &Vec3, angle: Real) -> Vec3 {
1752 let cos_a = angle.cos();
1753 let sin_a = angle.sin();
1754 v * cos_a + axis.cross(v) * sin_a + axis * axis.dot(v) * (1.0 - cos_a)
1755}
1756#[allow(dead_code)]
1765pub fn mat3_exp_skew(omega: &Vec3) -> Mat3 {
1766 let theta = omega.norm();
1767 if theta < 1e-12 {
1768 return Mat3::identity();
1769 }
1770 let axis = omega / theta;
1771 let s = skew_symmetric(&axis);
1772 let s2 = s * s;
1773 Mat3::identity() + s * theta.sin() + s2 * (1.0 - theta.cos())
1774}
1775#[allow(dead_code)]
1781pub fn mat3_log_rotation(r: &Mat3) -> Mat3 {
1782 let trace = r.trace();
1783 let cos_theta = ((trace - 1.0) / 2.0).clamp(-1.0, 1.0);
1784 let theta = cos_theta.acos();
1785 if theta.abs() < 1e-12 {
1786 return Mat3::zeros();
1787 }
1788 (r - r.transpose()) * (theta / (2.0 * theta.sin()))
1789}
1790#[allow(dead_code)]
1795pub fn quat_nlerp(a: &Quat, b: &Quat, t: Real) -> Quat {
1796 let ai = a.into_inner();
1797 let bi = b.into_inner();
1798 let bi = if ai.dot(&bi) < 0.0 { -bi } else { bi };
1799 let lerped = ai * (1.0 - t) + bi * t;
1800 UnitQuaternion::new_normalize(lerped)
1801}
1802#[allow(dead_code)]
1807pub fn quat_squad_control(q_prev: &Quat, q_curr: &Quat, q_next: &Quat) -> Quat {
1808 let qi_inv = q_curr.inverse();
1809 let log_next = quat_log(&(qi_inv * q_next));
1810 let log_prev = quat_log(&(qi_inv * q_prev));
1811 let sum = log_next + log_prev;
1812 let half = sum * (-0.25);
1813 q_curr * quat_exp(&half)
1814}
1815#[allow(dead_code)]
1819pub fn quat_geodesic_distance(a: &Quat, b: &Quat) -> Real {
1820 a.angle_to(b)
1821}
1822#[allow(dead_code)]
1827pub fn vec3_project_onto(v: &Vec3, onto_unit: &Vec3) -> Vec3 {
1828 onto_unit * v.dot(onto_unit)
1829}
1830#[allow(dead_code)]
1835pub fn vec3_reject_from(v: &Vec3, onto_unit: &Vec3) -> Vec3 {
1836 v - vec3_project_onto(v, onto_unit)
1837}
1838#[allow(dead_code)]
1840pub fn vec3_reflect_about(v: &Vec3, n: &Vec3) -> Vec3 {
1841 v - n * (2.0 * v.dot(n))
1842}
1843#[allow(dead_code)]
1849pub fn vec3_refract(v: &Vec3, n: &Vec3, eta: Real) -> Option<Vec3> {
1850 let cos_i = -(v.dot(n));
1851 let sin2_t = eta * eta * (1.0 - cos_i * cos_i);
1852 if sin2_t > 1.0 {
1853 return None;
1854 }
1855 let cos_t = (1.0 - sin2_t).sqrt();
1856 Some(v * eta + n * (eta * cos_i - cos_t))
1857}
1858#[allow(dead_code)]
1862pub fn vec3_angle_between(a: &Vec3, b: &Vec3) -> Real {
1863 let denom = a.norm() * b.norm();
1864 if denom < 1e-300 {
1865 return 0.0;
1866 }
1867 (a.dot(b) / denom).clamp(-1.0, 1.0).acos()
1868}
1869#[allow(dead_code)]
1871pub fn vec3_rotate_by_quat(v: &Vec3, q: &Quat) -> Vec3 {
1872 q.transform_vector(v)
1873}
1874#[allow(dead_code)]
1878pub fn mat3_from_axis_angle(axis: &Vec3, angle: Real) -> Mat3 {
1879 let c = angle.cos();
1880 let s = angle.sin();
1881 let t = 1.0 - c;
1882 let [x, y, z] = [axis.x, axis.y, axis.z];
1883 Mat3::new(
1884 t * x * x + c,
1885 t * x * y - s * z,
1886 t * x * z + s * y,
1887 t * x * y + s * z,
1888 t * y * y + c,
1889 t * y * z - s * x,
1890 t * x * z - s * y,
1891 t * y * z + s * x,
1892 t * z * z + c,
1893 )
1894}