molgfx_math/curves/
transport.rs1use crate::{CurveSample, Quat, Vec3};
4
5#[cfg(test)]
6#[path = "transport_tests.rs"]
7mod tests;
8
9#[derive(Clone, Copy, PartialEq, Debug)]
11pub struct TransportFrame {
12 pub tangent: Vec3,
14 pub normal: Vec3,
16 pub binormal: Vec3,
18}
19
20pub fn parallel_transport(samples: &[CurveSample], out: &mut Vec<TransportFrame>) {
23 out.clear();
24 let Some(first) = samples.first() else {
25 return;
26 };
27 let tangent = normalized(first.tangent, Vec3::Z);
28 let seed = least_aligned_axis(tangent);
29 let normal = normalized(seed.cross(tangent), Vec3::X);
30 out.push(frame(tangent, normal));
31 for sample in &samples[1..] {
32 let Some(previous) = out.last().copied() else {
33 break;
34 };
35 let next_tangent = normalized(sample.tangent, previous.tangent);
36 let rotation = Quat::from_rotation_arc(previous.tangent, next_tangent);
37 let normal = normalized(rotation * previous.normal, previous.normal);
42 out.push(TransportFrame {
43 tangent: next_tangent,
44 normal,
45 binormal: next_tangent.cross(normal),
46 });
47 }
48}
49
50#[inline]
51fn frame(tangent: Vec3, normal_hint: Vec3) -> TransportFrame {
52 let normal = normalized(normal_hint - tangent * normal_hint.dot(tangent), Vec3::X);
53 let binormal = normalized(tangent.cross(normal), Vec3::Y);
54 TransportFrame {
55 tangent,
56 normal: normalized(binormal.cross(tangent), normal),
57 binormal,
58 }
59}
60
61#[inline]
62fn normalized(value: Vec3, fallback: Vec3) -> Vec3 {
63 match value.try_normalize() {
64 Some(unit) => unit,
65 None => fallback,
66 }
67}
68
69#[inline]
70fn least_aligned_axis(tangent: Vec3) -> Vec3 {
71 let absolute = tangent.abs();
72 if absolute.x <= absolute.y && absolute.x <= absolute.z {
73 Vec3::X
74 } else if absolute.y <= absolute.z {
75 Vec3::Y
76 } else {
77 Vec3::Z
78 }
79}