SuperDex Physics C++ API
Loading...
Searching...
No Matches
basic_utils.h
Go to the documentation of this file.
1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/**********************************************************************
18
19 Base Utils:
20
21 This file contains generic template functions for scalar types (or other types with similar
22 operators).
23
24 Other overloads exist for types like NdArray, Matrix, and Simd. Such overloads are declared in
25 other headers, with their corresponding types. They generally perform the same operation, but
26 they do so member-wise.
27
28**/
29
30#pragma once
31
32// PLEASE DO NOT INCLUDE ADDITIONAL MOCHI HEADERS HERE
36// PLEASE DO NOT INCLUDE ADDITIONAL MOCHI HEADERS HERE
37
38#include <cmath>
39#include <cstring>
40#include <limits>
41#include <type_traits>
42
43namespace superdex {
44
45/**********************************************************************
46 Absolute Value
47*/
48
49template <typename T>
50[[nodiscard]] MOCHI_FORCE_INLINE constexpr T Abs(T a) {
51 return a >= T{0} ? a : -a;
52}
53
54/**********************************************************************
55 AllTrue & AnyTrue convert to bool
56*/
57
58// Return true if (a != 0). Simd overloads return true if (a[i] != 0) for all i.
59template <typename T>
60[[nodiscard]] MOCHI_FORCE_INLINE bool AllTrue(T const& a) {
61 return a != T{0};
62}
63
64// Return true if (a != 0). Simd overloads return true if (a[i] != 0) for ANY i.
65template <typename T>
66[[nodiscard]] MOCHI_FORCE_INLINE bool AnyTrue(T const& a) {
67 return a != T{0};
68}
69
70/**********************************************************************
71 Casting
72*/
73
74// StaticCast supports numeric conversions (e.g. truncate float to int)
75template <
76 typename To,
77 typename From,
78 MOCHI_CONCEPT(std::is_arithmetic_v<From>&& std::is_arithmetic_v<To>)>
79[[nodiscard]] MOCHI_FORCE_INLINE constexpr To StaticCast(From const& a) {
80 return static_cast<To>(a);
81}
82
83// Returns a pointer with the same address as the input pointer, but with a different type.
84// Equivalent to reinterpret_cast<From>(To).
85template <
86 typename To,
87 typename From,
88 MOCHI_CONCEPT(std::is_pointer_v<From>&& std::is_pointer_v<To>)>
89[[nodiscard]] MOCHI_FORCE_INLINE To ReinterpretCast(From const& a) {
90 static_assert(sizeof(To) == sizeof(From), "Invalid cast. Types must be the same size.");
91 return reinterpret_cast<To>(a);
92}
93
94// Returns an arithmetic type with the same bits as the input variable.
95// Example: auto var = ReinterpretCast<float>(-1); // var is a float with all bits set to 1
96template <
97 typename To,
98 typename From,
99 MOCHI_CONCEPT(std::is_arithmetic_v<From>&& std::is_arithmetic_v<To>)>
100[[nodiscard]] MOCHI_FORCE_INLINE To ReinterpretCast(From const& a) {
101 static_assert(sizeof(To) == sizeof(From), "Invalid cast. Types must be the same size.");
102 To b;
103 memcpy(&b, &a, sizeof(a));
104 return b;
105}
106
107/**********************************************************************
108 Fused Multiply-Add (and friends)
109*/
110
111template <typename A, typename B, typename C>
112[[nodiscard]] MOCHI_ANY MOCHI_FORCE_INLINE constexpr auto MulAdd(A a, B b, C c) {
113 return (a * b) + c;
114}
115
116template <typename A, typename B, typename C>
117[[nodiscard]] MOCHI_ANY MOCHI_FORCE_INLINE constexpr auto MulSub(A a, B b, C c) {
118 return (a * b) - c;
119}
120
121template <typename A, typename B, typename C>
122[[nodiscard]] MOCHI_ANY MOCHI_FORCE_INLINE constexpr auto NegMulAdd(A a, B b, C c) {
123 return -(a * b) + c;
124}
125
126template <typename A, typename B, typename C>
127[[nodiscard]] MOCHI_ANY MOCHI_FORCE_INLINE constexpr auto NegMulSub(A a, B b, C c) {
128 return -(a * b) - c;
129}
130
131/**********************************************************************
132 IsFinite
133*/
134
135template <typename T, MOCHI_CONCEPT(std::is_arithmetic_v<T>)>
136[[nodiscard]] MOCHI_FORCE_INLINE bool IsFinite(T a) {
137 if constexpr (std::is_integral_v<T>) {
138 return true;
139 } else {
140 return std::isfinite(a);
141 }
142}
143
144/**********************************************************************
145 Min, Max, Clamp, Lerp
146*/
147
148template <typename T>
149[[nodiscard]] MOCHI_ANY MOCHI_FORCE_INLINE constexpr T const& Min(T const& a, T const& b) {
150 return (a <= b) ? a : b;
151}
152
153// For convenience, you can find the minimum of an arbitrary number of values.
154template <typename T, typename... More>
155[[nodiscard]] MOCHI_FORCE_INLINE constexpr T Min(T a, T b, T c, More... args) {
156 return Min(a, Min(b, c, args...));
157}
158
159template <typename T>
160[[nodiscard]] MOCHI_ANY MOCHI_FORCE_INLINE constexpr T const& Max(T const& a, T const& b) {
161 return (a >= b) ? a : b;
162}
163
164// For convenience, you can find the maximum of an arbitrary number of values.
165template <typename T, typename... More>
166[[nodiscard]] MOCHI_FORCE_INLINE constexpr T Max(T a, T b, T c, More... args) {
167 return Max(a, Max(b, c, args...));
168}
169
170template <typename ValT, typename MinT, typename MaxT>
171[[nodiscard]] MOCHI_FORCE_INLINE constexpr ValT Clamp(ValT value, MinT min, MaxT max) {
172 return Min(ValT(max), Max(ValT(min), value));
173}
174
175template <typename ValT, typename MinT, typename MaxT, bool kMinInclusive, bool kMaxInclusive>
176[[nodiscard]] MOCHI_FORCE_INLINE constexpr ValT Rect(
177 ValT value,
178 MinT min,
179 MaxT max,
180 std::integral_constant<bool, kMinInclusive> /*minInclusive*/ = std::true_type{},
181 std::integral_constant<bool, kMaxInclusive> /*maxInclusive*/ = std::false_type{}) {
182 bool test1 = false;
183 if constexpr (kMinInclusive) {
184 test1 = (value >= min);
185 } else {
186 test1 = (value > min);
187 }
188 bool test2 = false;
189 if constexpr (kMaxInclusive) {
190 test2 = (value <= max);
191 } else {
192 test2 = (value < max);
193 }
194 return static_cast<ValT>(test1 && test2);
195}
196
197/**********************************************************************
198 Equality
199 */
200
201// clang-format off
202template<typename T> inline constexpr T kDefaultNearEqualEpsilon{0};
203template<> inline constexpr float kDefaultNearEqualEpsilon<float>{1.0e-6f};
204template<> inline constexpr double kDefaultNearEqualEpsilon<double>{1.0e-6}; // Historical value. Could probably be much smaller.
205// clang-format on
206
207template <typename T>
208[[nodiscard]] MOCHI_FORCE_INLINE constexpr auto Equal(T const& a, T const& b) {
209 return (a == b);
210}
211
212template <typename T>
213[[nodiscard]] MOCHI_FORCE_INLINE constexpr auto NotEqual(T const& a, T const& b) {
214 return !Equal(a, b);
215}
216
217template <typename T, MOCHI_CONCEPT(std::is_arithmetic_v<T>)>
218[[nodiscard]] MOCHI_FORCE_INLINE constexpr auto
219NearEqual(T const& a, T const& b, T epsilon = kDefaultNearEqualEpsilon<T>) {
220 static_assert(!std::is_same_v<std::remove_cv_t<T>, bool>, "NearEqual not supported for bool");
221 return Abs(a - b) <= epsilon;
222}
223
224template <typename T, MOCHI_CONCEPT(std::is_arithmetic_v<T>)>
225[[nodiscard]] MOCHI_FORCE_INLINE constexpr auto
226NearEqualRel(T const& a, T const& b, T epsilon = kDefaultNearEqualEpsilon<T>) {
227 static_assert(!std::is_same_v<std::remove_cv_t<T>, bool>, "NearEqualRel not supported for bool");
228 auto scaledEpsilon = epsilon * Max(Abs(a), Abs(b));
229 auto tolerance = Max(epsilon, scaledEpsilon);
230 return NearEqual(a, b, tolerance);
231}
232
233template <typename T, MOCHI_CONCEPT(std::is_arithmetic_v<T>)>
234[[nodiscard]] MOCHI_FORCE_INLINE constexpr auto NearZero(
235 T const& a,
236 T epsilon = kDefaultNearEqualEpsilon<T>) {
237 static_assert(!std::is_same_v<std::remove_cv_t<T>, bool>, "NearZero not supported for bool");
238 return Abs(a) <= epsilon;
239}
240
241/**********************************************************************
242 isize - Return a container size as int. Inspired by std::ssize.
243 http://en.cppreference.com/w/cpp/iterator/size
244*/
245
246// Generic version uses the size() member function.
247template <typename T>
248[[nodiscard]] MOCHI_FORCE_INLINE constexpr int isize(T const& a) {
249 return static_cast<int>(a.size());
250}
251
252// Specialization for c-style arrays
253template <typename T, size_t N>
254[[nodiscard]] MOCHI_FORCE_INLINE constexpr int isize([[maybe_unused]] T const (&array)[N]) {
255 return static_cast<int>(N);
256}
257
258/**********************************************************************
259 Linear Interpolation
260*/
261
262template <typename T, typename Frac>
263[[nodiscard]] MOCHI_FORCE_INLINE constexpr T Lerp(T a, T b, Frac t) {
264 return T(a * (Frac{1} - t) + b * t); // The faction t is not clamped
265}
266
267// Linearly remap a value from one number range to another. Does not clamp. If the value was outside
268// the input range, it will be outside the output range as well. You also remap an ascending range
269// onto a descending range or vice versa by setting (inA > inB) or (outA > outB).
270// Examples:
271// Remap(val, 0.0, 1.0, 0.0, 255.0); // As value goes from 0->1, result goes from 0->255
272// Remap(val, 1.0, 0.0, 0.0, 255.0); // As value goes from 1->0, result goes from 0->255
273template <typename T>
274[[nodiscard]] MOCHI_FORCE_INLINE constexpr T Remap(T value, T inA, T inB, T outA, T outB) {
275 return Lerp<T>(outA, outB, (value - inA) / (inB - inA));
276}
277
278// Similar to Remap (see above), except that the result is guaranteed to be between outA and outB.
279// Note that the output range might be ascending (outA < outB) or descending (outB < outA).
280// Examples:
281// RemapAndClamp(1.1, 0.0, 1.0, 0.0, 255.0); // Returns 255.0
282// RemapAndClamp(1.1, 0.0, 1.0, 255.0, 0.0); // Returns 0.0, as if value was 1.0 (not 1.1)
283template <typename T>
284[[nodiscard]] MOCHI_FORCE_INLINE constexpr T RemapAndClamp(T value, T inA, T inB, T outA, T outB) {
285 return Lerp<T>(outA, outB, Clamp<T>((value - inA) / (inB - inA), T{0}, T{1}));
286}
287
288/**********************************************************************
289 Powers
290*/
291
292template <typename B, typename E>
293[[nodiscard]] MOCHI_FORCE_INLINE constexpr B Pow(B base, E exp) {
294 return std::pow(base, exp);
295}
296
297template <typename T>
298[[nodiscard]] MOCHI_FORCE_INLINE constexpr T Sqr(T a) {
299 return a * a;
300}
301
302// Result as the same sign as the input
303template <typename T>
304[[nodiscard]] MOCHI_FORCE_INLINE constexpr T SignedSqr(T a) {
305 return a * Abs(a);
306}
307
308template <typename T>
309[[nodiscard]] MOCHI_FORCE_INLINE constexpr T Sqrt(T a) {
310 return std::sqrt(a);
311}
312
313// Result as the same sign as the input
314template <typename T>
315[[nodiscard]] MOCHI_FORCE_INLINE constexpr T SignedSqrt(T a);
316
317template <typename T>
318[[nodiscard]] MOCHI_FORCE_INLINE constexpr T IntegralSqrt(T a);
319
320template <typename T>
321[[nodiscard]] MOCHI_FORCE_INLINE constexpr bool IsPowerOfTwo(T a) {
322 static_assert(
323 std::is_integral_v<T> && !std::is_same_v<std::remove_cv_t<T>, bool>,
324 "IsPowerOfTwo requires a non-bool integral type");
325 return (a > T(0)) && ((a & (a - T(1))) == T(0));
326}
327
328// Returns the smallest power of two greater than or equal to a. Non-positive inputs return 1.
329// Positive inputs must not exceed the largest representable power of two.
330template <typename T>
331[[nodiscard]] MOCHI_FORCE_INLINE constexpr T NextPowerOfTwo(T a) {
332 static_assert(
333 std::is_integral_v<T> && !std::is_same_v<std::remove_cv_t<T>, bool>,
334 "NextPowerOfTwo requires a non-bool integral type");
335 [[maybe_unused]] constexpr T kMaxPowerOfTwo = T(1) << (std::numeric_limits<T>::digits - 1);
337 a <= kMaxPowerOfTwo, "Input exceeds the largest representable power of two.");
338 if (a <= T(0)) {
339 return T(1);
340 }
341 using UnsignedT = std::make_unsigned_t<T>;
342 static_assert(
343 std::numeric_limits<UnsignedT>::digits > 4 && std::numeric_limits<UnsignedT>::digits <= 64,
344 "Unsupported integral type");
345 auto result = static_cast<UnsignedT>(a - T(1));
346 result |= result >> 1;
347 result |= result >> 2;
348 result |= result >> 4;
349 if constexpr (std::numeric_limits<UnsignedT>::digits > 8) {
350 result |= result >> 8;
351 }
352 if constexpr (std::numeric_limits<UnsignedT>::digits > 16) {
353 result |= result >> 16;
354 }
355 if constexpr (std::numeric_limits<UnsignedT>::digits > 32) {
356 result |= result >> 32;
357 }
358 return static_cast<T>(result + UnsignedT(1));
359}
360
361/**********************************************************************
362 Reciprocal
363*/
364
366MOCHI_WARNING_IGNORE_MSVC(4723) // warning C4723: potential divide by 0
367
368template <typename T>
369[[nodiscard]] MOCHI_FORCE_INLINE constexpr T Rcp(T a) {
370 return T(1) / a;
371}
372
373// SIMD overloads of RcpApprox may have less precision. Use with caution.
374template <typename T>
375[[nodiscard]] MOCHI_FORCE_INLINE constexpr T RcpApprox(T a) {
376 return T(1) / a;
377}
378
380
381/**********************************************************************
382 Rounding
383*/
384
385template <typename T>
386[[nodiscard]] MOCHI_FORCE_INLINE constexpr T Floor(T a) {
387 return std::floor(a);
388}
389
390template <typename T>
391[[nodiscard]] MOCHI_FORCE_INLINE constexpr T Ceil(T a) {
392 return std::ceil(a);
393}
394
395template <typename T>
396[[nodiscard]] MOCHI_FORCE_INLINE constexpr T Round(T a) {
397 return std::round(a);
398}
399
400template <typename T>
401[[nodiscard]] MOCHI_FORCE_INLINE constexpr T RoundUp(T numToRound, T multiple) {
402 static_assert(
403 std::is_integral_v<T> && !std::is_same_v<std::remove_cv_t<T>, bool>, "Unsupported type");
404 return ((numToRound + multiple - 1) / multiple) * multiple;
405}
406
407template <typename T>
408[[nodiscard]] MOCHI_FORCE_INLINE constexpr T RoundDown(T numToRound, T multiple) {
409 static_assert(
410 std::is_integral_v<T> && !std::is_same_v<std::remove_cv_t<T>, bool>, "Unsupported type");
411 return (numToRound / multiple) * multiple;
412}
413
414/**********************************************************************
415 Select
416*/
417
418// Return (condition ? a : b).
419// Only supported for arithmetic types to prevent misuse. For other types, callers should explicitly
420// use a ternary expression if that's what they want. Other overloads do this per-member (e.g. for a
421// Matrix or Simd type).
422template <typename T>
423[[nodiscard]] MOCHI_FORCE_INLINE constexpr T Select(bool condition, T a, T b) {
424 static_assert(std::is_arithmetic_v<T>, "Unsupported type");
425 return condition ? a : b;
426}
427
428/**********************************************************************
429 Sign
430*/
431
432// Return +1 or -1 matching the sign of the given value.
433template <typename T>
434[[nodiscard]] MOCHI_FORCE_INLINE constexpr T Sign(T x) {
435 return Select(x >= T{}, T{1}, T{-1});
436}
437
438/**********************************************************************
439 Trigonometry
440*/
441
442template <typename T>
443[[nodiscard]] MOCHI_FORCE_INLINE constexpr T ASin(T a) {
444 return std::asin(a);
445}
446
447template <typename T>
448[[nodiscard]] MOCHI_FORCE_INLINE constexpr T Sin(T a) {
449 return std::sin(a);
450}
451
452template <typename T>
453[[nodiscard]] MOCHI_FORCE_INLINE constexpr T Sinc(T a);
454
455template <typename T>
456[[nodiscard]] MOCHI_FORCE_INLINE constexpr T Cos(T a) {
457 return std::cos(a);
458}
459
460template <typename T>
461[[nodiscard]] MOCHI_FORCE_INLINE constexpr T Tan(T a) {
462 return std::tan(a);
463}
464
465template <typename T>
466[[nodiscard]] MOCHI_FORCE_INLINE constexpr T ACos(T a) {
467 return std::acos(a);
468}
469
470template <typename T>
471[[nodiscard]] MOCHI_FORCE_INLINE constexpr T ATan(T a) {
472 return std::atan(a);
473}
474
475template <typename T>
476[[nodiscard]] MOCHI_FORCE_INLINE constexpr T ATan2(T y, T x) {
477 return std::atan2(y, x);
478}
479
480/**********************************************************************
481 Exponential-based
482*/
483
484template <typename T>
485[[nodiscard]] MOCHI_FORCE_INLINE constexpr T Exp(T a) {
486 return std::exp(a);
487}
488
489} // namespace superdex
490
491#include "basic_utils_inl.h"
#define MOCHI_ASSERT_VERBOSE(condition_without_side_effects,...)
Definition debug.h:102
#define MOCHI_CONCEPT(a)
#define MOCHI_WARNING_POP()
#define MOCHI_WARNING_IGNORE_MSVC(X)
#define MOCHI_WARNING_PUSH()
#define MOCHI_FORCE_INLINE
#define MOCHI_ANY
constexpr T ACos(T a)
constexpr bool IsPowerOfTwo(T a)
constexpr T const & Min(T const &a, T const &b)
constexpr T RoundDown(T numToRound, T multiple)
constexpr auto Equal(T const &a, T const &b)
constexpr T NextPowerOfTwo(T a)
constexpr T Sin(T a)
constexpr To StaticCast(From const &a)
Definition basic_utils.h:79
constexpr T kDefaultNearEqualEpsilon
constexpr ValT Rect(ValT value, MinT min, MaxT max, std::integral_constant< bool, kMinInclusive >=std::true_type{}, std::integral_constant< bool, kMaxInclusive >=std::false_type{})
bool AllTrue(T const &a)
Definition basic_utils.h:60
constexpr auto MulAdd(A a, B b, C c)
constexpr T ATan2(T y, T x)
constexpr T RoundUp(T numToRound, T multiple)
constexpr auto NotEqual(T const &a, T const &b)
constexpr T Exp(T a)
constexpr T Cos(T a)
constexpr T Abs(T a)
Definition basic_utils.h:50
constexpr auto MulSub(A a, B b, C c)
constexpr T Tan(T a)
bool AnyTrue(T const &a)
Definition basic_utils.h:66
constexpr T Select(bool condition, T a, T b)
constexpr T Sqr(T a)
constexpr T SignedSqrt(T a)
constexpr T Sqrt(T a)
constexpr T ATan(T a)
constexpr auto NegMulAdd(A a, B b, C c)
constexpr T Sign(T x)
constexpr T Floor(T a)
constexpr T ASin(T a)
constexpr T Ceil(T a)
constexpr T IntegralSqrt(T a)
constexpr ValT Clamp(ValT value, MinT min, MaxT max)
constexpr T SignedSqr(T a)
constexpr T RemapAndClamp(T value, T inA, T inB, T outA, T outB)
constexpr int isize(T const &a)
constexpr T Lerp(T a, T b, Frac t)
constexpr T const & Max(T const &a, T const &b)
constexpr T RcpApprox(T a)
bool NearEqual(TransformRT const &a, TransformRT const &b, real epsilon=kDefaultNearEqualEpsilon< real >)
constexpr auto NegMulSub(A a, B b, C c)
constexpr B Pow(B base, E exp)
constexpr auto NearEqualRel(T const &a, T const &b, T epsilon=kDefaultNearEqualEpsilon< T >)
constexpr T Rcp(T a)
constexpr T Sinc(T a)
bool IsFinite(TransformRT const &a)
constexpr T Remap(T value, T inA, T inB, T outA, T outB)
constexpr auto NearZero(T const &a, T epsilon=kDefaultNearEqualEpsilon< T >)
constexpr T Round(T a)