MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
Scalar.hpp
Go to the documentation of this file.
1#pragma once
2
3namespace MayaFlux::Kinesis {
4
5// =============================================================================
6// Range mapping
7// =============================================================================
8
9/**
10 * @brief Map @p x from [in_lo, in_hi] to [out_lo, out_hi], unclamped.
11 *
12 * Values outside [in_lo, in_hi] extrapolate linearly. Use map_clamped()
13 * when the output must stay within [out_lo, out_hi].
14 *
15 * Degenerate input range (in_lo == in_hi) returns out_lo.
16 */
17template <typename T>
18[[nodiscard]] constexpr T map(T x, T in_lo, T in_hi, T out_lo, T out_hi) noexcept
19{
20 const T in_range = in_hi - in_lo;
21 if (in_range == T { 0 })
22 return out_lo;
23 return out_lo + (x - in_lo) / in_range * (out_hi - out_lo);
24}
25
26/**
27 * @brief Map @p x from [in_lo, in_hi] to [out_lo, out_hi], clamped to
28 * [out_lo, out_hi].
29 */
30template <typename T>
31[[nodiscard]] constexpr T map_clamped(T x, T in_lo, T in_hi, T out_lo, T out_hi) noexcept
32{
33 const T v = map(x, in_lo, in_hi, out_lo, out_hi);
34 return std::clamp(v, std::min(out_lo, out_hi), std::max(out_lo, out_hi));
35}
36
37/**
38 * @brief Normalize @p x in [lo, hi] to [0, 1], unclamped.
39 *
40 * Distinct from Kinesis::Discrete::normalize, which operates in-place on
41 * a span and computes its own min/max. This is the scalar equivalent.
42 *
43 * Degenerate range (lo == hi) returns 0.
44 */
45template <typename T>
46[[nodiscard]] constexpr T normalize(T x, T lo, T hi) noexcept
47{
48 const T range = hi - lo;
49 if (range == T { 0 })
50 return T { 0 };
51 return (x - lo) / range;
52}
53
54/**
55 * @brief Denormalize @p t from [0, 1] to [lo, hi].
56 *
57 * Equivalent to glm::mix(lo, hi, t) but named for legibility at call sites
58 * where the intent is range reconstruction rather than interpolation.
59 */
60template <typename T>
61[[nodiscard]] constexpr T denormalize(T t, T lo, T hi) noexcept
62{
63 return lo + t * (hi - lo);
64}
65
66// =============================================================================
67// Smoothing
68// =============================================================================
69
70/**
71 * @brief GLSL smoothstep: Hermite interpolation in [lo, hi].
72 *
73 * Returns 0 when x <= lo, 1 when x >= hi, and a smooth cubic curve
74 * in between. Equivalent to GLSL smoothstep(lo, hi, x).
75 *
76 * GLM provides this as glm::smoothstep but requires glm/gtx/compatibility.hpp
77 * and a vector type. This overload accepts any scalar T.
78 */
79template <typename T>
80[[nodiscard]] constexpr T smoothstep(T lo, T hi, T x) noexcept
81{
82 const T t = std::clamp((x - lo) / (hi - lo), T { 0 }, T { 1 });
83 return t * t * (T { 3 } - T { 2 } * t);
84}
85
86/**
87 * @brief Ken Perlin's quintic smootherstep: C2-continuous in [lo, hi].
88 *
89 * Derivative is zero at both endpoints (unlike smoothstep which has zero
90 * first derivative only). Use when second-derivative continuity matters:
91 * camera motion, envelope transitions, SDF blending weights.
92 */
93template <typename T>
94[[nodiscard]] constexpr T smootherstep(T lo, T hi, T x) noexcept
95{
96 const T t = std::clamp((x - lo) / (hi - lo), T { 0 }, T { 1 });
97 return t * t * t * (t * (t * T { 6 } - T { 15 }) + T { 10 });
98}
99
100/**
101 * @brief Exponential smoothing: move @p current toward @p target at rate
102 * @p smoothing over time step @p dt.
103 *
104 * @p smoothing is a half-life in seconds: the distance to target halves
105 * every @p smoothing seconds. At smoothing = 0.1 the value tracks quickly;
106 * at smoothing = 2.0 it lags heavily.
107 *
108 * Framerate-independent. Equivalent to the "lerp every frame" pattern
109 * done correctly:
110 * current = lerp(current, target, 1 - exp(-dt / smoothing))
111 *
112 * Degenerate case (smoothing <= 0) snaps to target immediately.
113 */
114template <typename T>
115[[nodiscard]] inline T damp(T current, T target, T smoothing, T dt) noexcept
116{
117 if (smoothing <= T { 0 })
118 return target;
119 return current + (target - current) * (T { 1 } - std::exp(-dt / smoothing));
120}
121
122// =============================================================================
123// Wrapping and folding
124// =============================================================================
125
126/**
127 * @brief Wrap @p x into [lo, hi) with modulo semantics.
128 *
129 * Unlike std::fmod, handles negative values and arbitrary lo correctly.
130 * Equivalent to the GLSL mod() applied after shifting by lo.
131 *
132 * Example: wrap(-0.1F, 0.F, 1.F) -> 0.9F
133 */
134template <typename T>
135[[nodiscard]] inline T wrap(T x, T lo, T hi) noexcept
136{
137 const T range = hi - lo;
138 if (range <= T { 0 })
139 return lo;
140 return x - range * std::floor((x - lo) / range);
141}
142
143/**
144 * @brief Ping-pong (triangle wave): fold @p x back and forth in [lo, hi].
145 *
146 * At x = lo the value is lo. It increases to hi then folds back toward lo.
147 * Period is 2 * (hi - lo). Use for oscillating parameters, bounce animations,
148 * or any signal that should reverse at the boundary instead of wrapping.
149 *
150 * Example: ping_pong(1.3F, 0.F, 1.F) -> 0.7F
151 */
152template <typename T>
153[[nodiscard]] inline T ping_pong(T x, T lo, T hi) noexcept
154{
155 const T range = hi - lo;
156 if (range <= T { 0 })
157 return lo;
158 const T shifted = x - lo;
159 const T period = T { 2 } * range;
160 const T t = shifted - period * std::floor(shifted / period);
161 return lo + (t < range ? t : period - t);
162}
163
164// =============================================================================
165// Snapping
166// =============================================================================
167
168/**
169 * @brief Round @p x to the nearest multiple of @p step.
170 *
171 * std::round(x / step) * step but named. step must be > 0.
172 *
173 * Example: snap(0.73F, 0.25F) -> 0.75F
174 */
175template <typename T>
176[[nodiscard]] inline T snap(T x, T step) noexcept
177{
178 if (step <= T { 0 })
179 return x;
180 return std::round(x / step) * step;
181}
182
183/**
184 * @brief Snap @p x to the nearest multiple of @p step within [lo, hi].
185 */
186template <typename T>
187[[nodiscard]] inline T snap(T x, T lo, T hi, T step) noexcept
188{
189 return std::clamp(snap(x, step), lo, hi);
190}
191
192// =============================================================================
193// Easing
194// =============================================================================
195
196/**
197 * @brief Cubic ease-in: slow start, fast end.
198 * @param t Normalized time in [0, 1].
199 */
200template <typename T>
201[[nodiscard]] constexpr T ease_in(T t) noexcept
202{
203 return t * t * t;
204}
205
206/**
207 * @brief Cubic ease-out: fast start, slow end.
208 * @param t Normalized time in [0, 1].
209 */
210template <typename T>
211[[nodiscard]] constexpr T ease_out(T t) noexcept
212{
213 const T u = T { 1 } - t;
214 return T { 1 } - u * u * u;
215}
216
217/**
218 * @brief Cubic ease-in-out: slow start, fast middle, slow end.
219 * @param t Normalized time in [0, 1].
220 */
221template <typename T>
222[[nodiscard]] constexpr T ease_in_out(T t) noexcept
223{
224 return t < T { 0.5 }
225 ? T { 4 } * t * t * t
226 : T { 1 } - T { 4 } * (T { 1 } - t) * (T { 1 } - t) * (T { 1 } - t) * T { 0.5 };
227}
228
229// =============================================================================
230// Deadzone and sign
231// =============================================================================
232
233/**
234 * @brief Zero values within [-threshold, threshold] and rescale the remainder
235 * to fill [0, 1] (or [-1, 1] for negative inputs).
236 *
237 * Standard controller/HID deadzone. Input and output are in [-1, 1].
238 * threshold must be in [0, 1).
239 *
240 * Example: deadzone(0.1F, 0.2F) -> 0.0F (inside dead zone)
241 * deadzone(0.6F, 0.2F) -> 0.5F (rescaled from [0.2, 1.0] to [0, 1])
242 */
243template <typename T>
244[[nodiscard]] constexpr T deadzone(T x, T threshold) noexcept
245{
246 if (std::abs(x) <= threshold)
247 return T { 0 };
248 const T sign = x > T { 0 } ? T { 1 } : T { -1 };
249 return sign * (std::abs(x) - threshold) / (T { 1 } - threshold);
250}
251
252/**
253 * @brief Sign of @p x, returning -1 or +1, never 0.
254 *
255 * std::copysign(1, x) but readable. Useful when zero must be treated as
256 * positive (e.g. initial state of a direction accumulator).
257 */
258template <typename T>
259[[nodiscard]] constexpr T sign_nonzero(T x) noexcept
260{
261 return x < T { 0 } ? T { -1 } : T { 1 };
262}
263
264} // namespace MayaFlux::Kinesis
glm::vec2 current
T ping_pong(T x, T lo, T hi) noexcept
Ping-pong (triangle wave): fold x back and forth in [lo, hi].
Definition Scalar.hpp:153
T damp(T current, T target, T smoothing, T dt) noexcept
Exponential smoothing: move current toward target at rate smoothing over time step dt.
Definition Scalar.hpp:115
constexpr T denormalize(T t, T lo, T hi) noexcept
Denormalize t from [0, 1] to [lo, hi].
Definition Scalar.hpp:61
constexpr T normalize(T x, T lo, T hi) noexcept
Normalize x in [lo, hi] to [0, 1], unclamped.
Definition Scalar.hpp:46
T snap(T x, T step) noexcept
Round x to the nearest multiple of step.
Definition Scalar.hpp:176
constexpr T ease_in(T t) noexcept
Cubic ease-in: slow start, fast end.
Definition Scalar.hpp:201
constexpr T smootherstep(T lo, T hi, T x) noexcept
Ken Perlin's quintic smootherstep: C2-continuous in [lo, hi].
Definition Scalar.hpp:94
Tendency< D, float > threshold(const Tendency< D, float > &t, float thresh)
Zero output below threshold, pass through above.
Definition Tendency.hpp:141
constexpr T ease_in_out(T t) noexcept
Cubic ease-in-out: slow start, fast middle, slow end.
Definition Scalar.hpp:222
constexpr T map(T x, T in_lo, T in_hi, T out_lo, T out_hi) noexcept
Map x from [in_lo, in_hi] to [out_lo, out_hi], unclamped.
Definition Scalar.hpp:18
constexpr T ease_out(T t) noexcept
Cubic ease-out: fast start, slow end.
Definition Scalar.hpp:211
constexpr T map_clamped(T x, T in_lo, T in_hi, T out_lo, T out_hi) noexcept
Map x from [in_lo, in_hi] to [out_lo, out_hi], clamped to [out_lo, out_hi].
Definition Scalar.hpp:31
constexpr T smoothstep(T lo, T hi, T x) noexcept
GLSL smoothstep: Hermite interpolation in [lo, hi].
Definition Scalar.hpp:80
T wrap(T x, T lo, T hi) noexcept
Wrap x into [lo, hi) with modulo semantics.
Definition Scalar.hpp:135
constexpr T sign_nonzero(T x) noexcept
Sign of x, returning -1 or +1, never 0.
Definition Scalar.hpp:259
constexpr T deadzone(T x, T threshold) noexcept
Zero values within [-threshold, threshold] and rescale the remainder to fill [0, 1] (or [-1,...
Definition Scalar.hpp:244