molgfx_math/curves/
spline.rs1use crate::Vec3;
4
5#[cfg(test)]
6#[path = "spline_tests.rs"]
7mod tests;
8
9#[derive(Clone, Copy, PartialEq, Debug)]
11pub struct CurveSample {
12 pub position: Vec3,
14 pub tangent: Vec3,
16 pub segment: u32,
18 pub parameter: f32,
20}
21
22pub fn sample_catmull_rom(
26 points: &[Vec3],
27 tolerance: f32,
28 max_steps: u8,
29 out: &mut Vec<CurveSample>,
30) {
31 sample_catmull_rom_demanding(points, tolerance, max_steps, &[], out);
32}
33
34pub fn sample_catmull_rom_demanding(
49 points: &[Vec3],
50 tolerance: f32,
51 max_steps: u8,
52 demand: &[f32],
53 out: &mut Vec<CurveSample>,
54) {
55 out.clear();
56 if points.len() < 2 {
57 if let Some(&position) = points.first() {
58 out.push(CurveSample {
59 position,
60 tangent: Vec3::Z,
61 segment: 0,
62 parameter: 0.0,
63 });
64 }
65 return;
66 }
67 let maximum = max_steps.max(1);
68 for segment in 0..points.len() - 1 {
69 let curve = segment_points(points, segment);
70 let extra = match demand.get(segment) {
71 Some(value) if value.is_finite() => value.max(0.0),
72 _ => 0.0,
73 };
74 let steps = adaptive_steps(curve, tolerance.max(1e-4), maximum, extra);
75 let start = u8::from(segment != 0);
76 for step in start..=steps {
77 let t = f32::from(step) / f32::from(steps);
78 let derivative = tangent(curve, t);
79 out.push(CurveSample {
80 position: position(curve, t),
81 tangent: normalized(derivative, Vec3::Z),
82 segment: u32::try_from(segment)
83 .into_iter()
84 .fold(u32::MAX, |_, value| value),
85 parameter: t,
86 });
87 }
88 }
89}
90
91pub fn sample_catmull_rom_fixed(
97 points: &[Vec3],
98 steps_per_segment: u8,
99 out: &mut Vec<CurveSample>,
100) {
101 out.clear();
102 if points.len() < 2 {
103 if let Some(&position) = points.first() {
104 out.push(CurveSample {
105 position,
106 tangent: Vec3::Z,
107 segment: 0,
108 parameter: 0.0,
109 });
110 }
111 return;
112 }
113 let steps = steps_per_segment.max(1);
114 for segment in 0..points.len() - 1 {
115 let curve = segment_points(points, segment);
116 let start = u8::from(segment != 0);
117 for step in start..=steps {
118 let t = f32::from(step) / f32::from(steps);
119 out.push(CurveSample {
120 position: position(curve, t),
121 tangent: normalized(tangent(curve, t), Vec3::Z),
122 segment: u32::try_from(segment)
123 .into_iter()
124 .fold(u32::MAX, |_, value| value),
125 parameter: t,
126 });
127 }
128 }
129}
130
131#[inline]
132fn segment_points(points: &[Vec3], segment: usize) -> [Vec3; 4] {
133 let last = points.len() - 1;
134 [
135 points[segment.saturating_sub(1)],
136 points[segment],
137 points[(segment + 1).min(last)],
138 points[(segment + 2).min(last)],
139 ]
140}
141
142#[inline]
143fn adaptive_steps(points: [Vec3; 4], tolerance: f32, maximum: u8, extra: f32) -> u8 {
144 let midpoint = position(points, 0.5);
145 let chord_midpoint = (points[1] + points[2]) * 0.5;
146 let deviation = midpoint.distance(chord_midpoint);
147 let start = normalized(tangent(points, 0.0), Vec3::Z);
148 let end = normalized(tangent(points, 1.0), start);
149 let turn = start.dot(end).clamp(-1.0, 1.0).acos();
150 let demand = (deviation / tolerance).sqrt() + turn * 2.0 + extra;
151 let mut steps = 1u8;
152 while steps < maximum && f32::from(steps) < demand {
153 steps = steps.saturating_mul(2).min(maximum);
154 }
155 steps
156}
157
158#[inline]
159fn normalized(value: Vec3, fallback: Vec3) -> Vec3 {
160 match value.try_normalize() {
161 Some(unit) => unit,
162 None => fallback,
163 }
164}
165
166#[inline]
167fn position(points: [Vec3; 4], t: f32) -> Vec3 {
168 let [p0, p1, p2, p3] = points;
169 let t2 = t * t;
170 let t3 = t2 * t;
171 (p1 * 2.0
172 + (p2 - p0) * t
173 + (p0 * 2.0 - p1 * 5.0 + p2 * 4.0 - p3) * t2
174 + (-p0 + p1 * 3.0 - p2 * 3.0 + p3) * t3)
175 * 0.5
176}
177
178#[inline]
179fn tangent(points: [Vec3; 4], t: f32) -> Vec3 {
180 let [p0, p1, p2, p3] = points;
181 let t2 = t * t;
182 ((p2 - p0)
183 + (p0 * 4.0 - p1 * 10.0 + p2 * 8.0 - p3 * 2.0) * t
184 + (-p0 * 3.0 + p1 * 9.0 - p2 * 9.0 + p3 * 3.0) * t2)
185 * 0.5
186}