inox2d/math/
interp.rs

1use glam::Vec2;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub enum InterpolateMode {
5	/// Round to nearest
6	Nearest,
7	/// Linear interpolation
8	Linear,
9	// there's more but I'm not adding them for now.
10}
11
12#[derive(Debug, Clone, thiserror::Error)]
13#[error("Unknown interpolate mode {0:?}")]
14pub struct UnknownInterpolateModeError(String);
15
16impl TryFrom<&str> for InterpolateMode {
17	type Error = UnknownInterpolateModeError;
18
19	fn try_from(value: &str) -> Result<Self, Self::Error> {
20		match value {
21			"Linear" => Ok(InterpolateMode::Linear),
22			unknown => Err(UnknownInterpolateModeError(unknown.to_owned())),
23		}
24	}
25}
26
27#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
28pub struct InterpRange<T> {
29	pub beg: T,
30	pub end: T,
31}
32
33impl<T> InterpRange<T> {
34	#[inline]
35	pub fn new(beg: T, end: T) -> Self {
36		Self { beg, end }
37	}
38}
39
40impl InterpRange<Vec2> {
41	#[inline]
42	pub fn to_x(self) -> InterpRange<f32> {
43		InterpRange {
44			beg: self.beg.x,
45			end: self.end.x,
46		}
47	}
48
49	#[inline]
50	pub fn to_y(self) -> InterpRange<f32> {
51		InterpRange {
52			beg: self.beg.y,
53			end: self.end.y,
54		}
55	}
56}
57
58#[inline]
59fn interpolate_nearest(t: f32, range_in: InterpRange<f32>, range_out: InterpRange<f32>) -> f32 {
60	debug_assert!(
61		range_in.beg <= t && t <= range_in.end,
62		"{} <= {} <= {}",
63		range_in.beg,
64		t,
65		range_in.end
66	);
67
68	if (range_in.end - t) < (t - range_in.beg) {
69		range_out.end
70	} else {
71		range_out.beg
72	}
73}
74
75#[inline]
76fn interpolate_linear(t: f32, range_in: InterpRange<f32>, range_out: InterpRange<f32>) -> f32 {
77	debug_assert!(
78		range_in.beg <= t && t <= range_in.end,
79		"{} <= {} <= {}",
80		range_in.beg,
81		t,
82		range_in.end
83	);
84
85	(t - range_in.beg) * (range_out.end - range_out.beg) / (range_in.end - range_in.beg) + range_out.beg
86}
87
88#[inline]
89pub fn interpolate_f32(t: f32, range_in: InterpRange<f32>, range_out: InterpRange<f32>, mode: InterpolateMode) -> f32 {
90	match mode {
91		InterpolateMode::Nearest => interpolate_nearest(t, range_in, range_out),
92		InterpolateMode::Linear => interpolate_linear(t, range_in, range_out),
93	}
94}
95
96#[inline]
97pub fn interpolate_vec2(
98	t: f32,
99	range_in: InterpRange<f32>,
100	range_out: InterpRange<Vec2>,
101	mode: InterpolateMode,
102) -> Vec2 {
103	let x = interpolate_f32(t, range_in, range_out.to_x(), mode);
104	let y = interpolate_f32(t, range_in, range_out.to_y(), mode);
105	Vec2 { x, y }
106}
107
108pub fn interpolate_f32s_additive(
109	t: f32,
110	range_in: InterpRange<f32>,
111	range_out: InterpRange<&[f32]>,
112	mode: InterpolateMode,
113	out: &mut [f32],
114) {
115	for ((&ob, &oe), o) in range_out.beg.iter().zip(range_out.end).zip(out) {
116		*o += interpolate_f32(t, range_in, InterpRange::new(ob, oe), mode);
117	}
118}
119
120pub fn interpolate_vec2s_additive(
121	t: f32,
122	range_in: InterpRange<f32>,
123	range_out: InterpRange<&[Vec2]>,
124	mode: InterpolateMode,
125	out: &mut [Vec2],
126) {
127	for ((&ob, &oe), o) in range_out.beg.iter().zip(range_out.end).zip(out) {
128		*o += interpolate_vec2(t, range_in, InterpRange::new(ob, oe), mode);
129	}
130}
131
132#[inline]
133pub fn bi_interpolate_f32(
134	t: Vec2,
135	range_in: InterpRange<Vec2>,
136	out_top: InterpRange<f32>,
137	out_bottom: InterpRange<f32>,
138	mode: InterpolateMode,
139) -> f32 {
140	let beg = interpolate_f32(t.x, range_in.to_x(), out_top, mode);
141	let end = interpolate_f32(t.x, range_in.to_x(), out_bottom, mode);
142	interpolate_f32(t.y, range_in.to_y(), InterpRange::new(beg, end), mode)
143}
144
145#[inline]
146pub fn bi_interpolate_vec2(
147	t: Vec2,
148	range_in: InterpRange<Vec2>,
149	out_top: InterpRange<Vec2>,
150	out_bottom: InterpRange<Vec2>,
151	mode: InterpolateMode,
152) -> Vec2 {
153	let beg = interpolate_vec2(t.x, range_in.to_x(), out_top, mode);
154	let end = interpolate_vec2(t.x, range_in.to_x(), out_bottom, mode);
155	interpolate_vec2(t.y, range_in.to_y(), InterpRange::new(beg, end), mode)
156}
157
158pub fn bi_interpolate_f32s_additive(
159	t: Vec2,
160	range_in: InterpRange<Vec2>,
161	out_top: InterpRange<&[f32]>,
162	out_bottom: InterpRange<&[f32]>,
163	mode: InterpolateMode,
164	out: &mut [f32],
165) {
166	for (((&otb, &ote), (&obb, &obe)), o) in (out_top.beg.iter().zip(out_top.end))
167		.zip(out_bottom.beg.iter().zip(out_bottom.end))
168		.zip(out)
169	{
170		*o += bi_interpolate_f32(
171			t,
172			range_in,
173			InterpRange::new(otb, ote),
174			InterpRange::new(obb, obe),
175			mode,
176		)
177	}
178}
179
180pub fn bi_interpolate_vec2s_additive(
181	t: Vec2,
182	range_in: InterpRange<Vec2>,
183	out_top: InterpRange<&[Vec2]>,
184	out_bottom: InterpRange<&[Vec2]>,
185	mode: InterpolateMode,
186	out: &mut [Vec2],
187) {
188	for (((&otb, &ote), (&obb, &obe)), o) in (out_top.beg.iter().zip(out_top.end))
189		.zip(out_bottom.beg.iter().zip(out_bottom.end))
190		.zip(out)
191	{
192		*o += bi_interpolate_vec2(
193			t,
194			range_in,
195			InterpRange::new(otb, ote),
196			InterpRange::new(obb, obe),
197			mode,
198		)
199	}
200}
201
202#[cfg(test)]
203mod tests {
204	use super::*;
205
206	#[test]
207	fn test_linear_interpolation() {
208		assert_eq!(
209			interpolate_linear(0.0, InterpRange::new(0.0, 1.0), InterpRange::new(-5.0, 5.0)),
210			-5.0
211		);
212		assert_eq!(
213			interpolate_linear(1.0, InterpRange::new(0.0, 1.0), InterpRange::new(-5.0, 5.0)),
214			5.0
215		);
216		assert_eq!(
217			interpolate_linear(0.5, InterpRange::new(0.0, 1.0), InterpRange::new(-5.0, 5.0)),
218			0.0
219		);
220		assert_eq!(
221			interpolate_linear(0.0, InterpRange::new(0.5, 0.0), InterpRange::new(-5.0, 5.0)),
222			5.0
223		);
224	}
225}