JPL Spatial
Sound spatialization and propagation library
Loading...
Searching...
No Matches
Vec3Math.h
Go to the documentation of this file.
1//
2// ██╗██████╗ ██╗ ██╗██████╗ ███████╗
3// ██║██╔══██╗ ██║ ██║██╔══██╗██╔════╝ ** JPLSpatial **
4// ██║██████╔╝ ██║ ██║██████╔╝███████╗
5// ██ ██║██╔═══╝ ██║ ██║██╔══██╗╚════██║ https://github.com/Jaytheway/JPLSpatial
6// ╚█████╔╝██║ ███████╗██║██████╔╝███████║
7// ╚════╝ ╚═╝ ╚══════╝╚═╝╚═════╝ ╚══════╝
8//
9// Copyright 2024 Jaroslav Pevno, JPLSpatial is offered under the terms of the ISC license:
10//
11// Permission to use, copy, modify, and/or distribute this software for any purpose with or
12// without fee is hereby granted, provided that the above copyright notice and this permission
13// notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
14// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
15// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
16// CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
17// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
18// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19
20#pragma once
21
27
28#include <cmath>
29#include <concepts>
30#include <numbers>
31#include <limits>
32
34{
35 template<CVec3 Vec3>
36 using FloatOf = std::remove_cvref_t<decltype(GetX(std::declval<Vec3>()))>;
37}
38
39namespace JPL::Math
40{
41 template<CVec3 Vec3>
42 [[nodiscard]] JPL_INLINE constexpr bool IsNearlyEqual(const Vec3& a,
43 const Vec3& b,
45 {
46 return IsNearlyEqual(GetX(a), GetX(b), tolerance)
48 && IsNearlyEqual(GetZ(a), GetZ(b), tolerance);
49 }
50
51 template<CVec3 Vec3>
52 [[nodiscard]] JPL_INLINE constexpr bool IsNearlyZero(const Vec3& a,
54 {
55 return IsNearlyEqual(a, Vec3(0, 0, 0));
56 }
57
58 template<CVec3 Vec3>
59 [[nodiscard]] JPL_INLINE bool HasNans(const Vec3& vec)
60 {
61 return std::isnan(GetX(vec))
62 || std::isnan(GetY(vec))
63 || std::isnan(GetZ(vec));
64 }
65
66 template<CVec3 Vec3>
67 [[nodiscard]] inline Vec3 GetNormalizedPerpendicular(const Vec3& vec) noexcept
68 {
69 using FloatType = Internal::FloatOf<Vec3>;
70
71 if (Abs(GetX(vec)) > Abs(GetY(vec)))
72 {
73 const FloatType len = Math::Sqrt(GetX(vec) * GetX(vec) + GetZ(vec) * GetZ(vec));
74 return Vec3(GetZ(vec), FloatType(0.0), -GetX(vec)) / len;
75 }
76 else
77 {
78 const FloatType len = Math::Sqrt(GetY(vec) * GetY(vec) + GetZ(vec) * GetZ(vec));
79 return Vec3(FloatType(0.0), GetZ(vec), -GetY(vec)) / len;
80 }
81 };
82
83 template<CVec3 Vec3>
84 inline void CreateOrthonormalBasis(const Vec3& normal, Vec3& tangent, Vec3& bitangent) noexcept
85 {
86 using FloatType = Internal::FloatOf<Vec3>;
87
88 if (Abs(GetX(normal)) > Abs(GetY(normal)))
89 {
90 // normal.X is largest component
91 tangent = Vec3(-GetZ(normal), FloatType(0.0), GetX(normal));
92 }
93 else
94 {
95 // normal.Y is largest component
96 tangent = Vec3(FloatType(0.0), GetZ(normal), -GetY(normal));
97 }
98 Normalize(tangent);
99 bitangent = CrossProduct(normal, tangent); // right-handed basis
100 }
101
106 template<CVec3 Vec3>
107 [[nodiscard]] JPL_INLINE Vec3 RotateVector(const Vec3& vector, const Vec3& rotationAxis, float angleRad) noexcept
108 {
109 const auto [sinTheta, cosTheta] = Math::SinCos(angleRad);
110 return vector * cosTheta
111 + CrossProduct(rotationAxis, vector) * sinTheta
112 + rotationAxis * DotProduct(rotationAxis, vector) * (1.0f - cosTheta);
113 }
114
115 template<CVec3 Vec3>
116 [[nodiscard]] JPL_INLINE Vec3 Lerp(const Vec3& v0, const Vec3& v1, float t) noexcept
117 {
118 return v0 + t * (v1 - v0);
119 }
120
121 [[nodiscard]] JPL_INLINE Vec3Pack Lerp(const Vec3Pack& v0, const Vec3Pack& v1, simd t) noexcept
122 {
123 //return v0 + t * (v1 - v0);
124 return FMA(t, (v1 - v0), v0);
125 }
126
129 template<CVec3 Vec3>
130 [[nodiscard]] JPL_INLINE Vec3 Nlerp(const Vec3& v0, const Vec3& v1, float t) noexcept
131 {
132 return Normalized(Lerp(v0, v1, t));
133 }
134
136 {
137 return Normalized(Lerp(v0, v1, t));
138 }
139
141 template<CVec3 Vec3>
142 [[nodiscard]] inline Vec3 Slerp(const Vec3& v0, const Vec3& v1, float t) noexcept
143 {
144 using FloatType = Internal::FloatOf<Vec3>;
145 FloatType dot = DotProduct(v0, v1);
146
147 // Clamp dot product to handle floating point inaccuracies
148 // (values slightly outside [-1, 1] can cause acos to return NaN)
149 dot = std::fmax(FloatType(-1.0), std::fmin(FloatType(1.0), dot));
150
151 static constexpr FloatType EPSILON =
152 std::numeric_limits<FloatType>::epsilon() * FloatType(100.0);
153
154 // Case 1: Vectors are very close (parallel or nearly parallel)
155 // This provides good enough results for nearly parallel vectors.
156 if (dot > FloatType(1.0) - EPSILON)
157 {
158 // Use linear interpolation and re-normalize (NLERP)
159 //return (v0 * (FloatType(1.0) - t) + v1 * t).Normalize();
160 return Nlerp(v0, v1, t);
161 }
162
163 // Case 2: Vectors are opposite (dot product approx -1)
164 if (dot < FloatType(-1.0) + EPSILON)
165 {
166 // When vectors are opposite, SLERP is ambiguous.
167 // We pick an arbitrary axis perpendicular to v0 to rotate around.
168 Vec3 axis = Vec3(1.0, 0.0, 0.0);
169 if (Math::Abs(DotProduct(v0, axis)) > FloatType(1.0) - EPSILON)
170 {
171 // If v0 is too close to X-axis, try Y-axis
172 axis = Vec3(0.0, 1.0, 0.0);
173 }
174
175#if 0
176 // The rotation axis is perpendicular to v0.
177 axis = CrossProduct(v0, axis);
178 const auto proj = CrossProduct(axis, v0);
179#else
180 // proj = basis projected onto plane perpendicular to v0
181 const auto proj = Normalized((axis - v0 * DotProduct(v0, axis)));
182#endif
183
184 // Rotate v0 by t * 180 degrees around the chosen axis.
185 // This is equivalent to a half-rotation of a quaternion.
186 const FloatType angle = t * std::numbers::pi_v<FloatType>;
187 const auto [sinAngle, cosAngle] = Math::SinCos(angle);
188
189 // Optimized for unit vectors and axis perpendicular to v:
190 // (axis . v) = 0
191 // So: v_rot = v * cos(a) + (axis x v) * sin(a)
192 return Normalized(v0 * cosAngle + proj * sinAngle);
193 }
194
195 // Case 3: General case (vectors are neither very close nor opposite)
196 const FloatType theta = std::acos(dot); // Angle between vectors
197 const FloatType invSinTheta = 1.0f / std::sin(theta); // Will not be close to zero here
198
199 const FloatType w0 = std::sin((FloatType(1.0) - t) * theta) * invSinTheta;
200 const FloatType w1 = std::sin(t * theta) * invSinTheta;
201
202 return Normalized(v0 * w0 + v1 * w1);
203 }
204
206 inline void Slerp(
208 const Vec3Pack& V1,
209 const simd& t) noexcept
210 {
211 using FloatType = simd;
212 static const FloatType one(1.0f);
213
214 // Clamp dot product to handle floating point inaccuracies
215 // (values slightly outside [-1, 1] can cause acos to return NaN)
216 const FloatType dot = clamp(DotProduct(inOutV0, V1), -one, one);
217
218 static constexpr float EPSILON =
219 (std::numeric_limits<float>::epsilon() * 100.0f);
220
221 static const FloatType oneMinusEps(1.0f - EPSILON);
222 static const FloatType minusOnePlusEps(-1.0f + EPSILON);
223
224 // Case 1: Vectors are very close (parallel or nearly parallel)
225 // This provides good enough results for nearly parallel vectors.
227
228 // Case 2: Vectors are opposite (dot product approx -1)
230
231 // Opposite Case
232 // --------------------------------------------------------------
233 // When vectors are opposite, SLERP is ambiguous.
234 // We pick an arbitrary axis perpendicular to v0 to rotate around.
235 Vec3Pack axis(1.0f, 0.0f, 0.0f);
236
237 // If v0 is too close to X-axis, try Y-axis
238 const simd_mask isClose = abs(DotProduct(inOutV0, axis)) > oneMinusEps;
239 axis = {
242 simd::c_0() // Z
243 };
244
245#if 0
246 // The rotation axis is perpendicular to v0.
247 axis = CrossProduct(inOutV0, axis);
248 const Vec3Pack proj = CrossProduct(axis, inOutV0)
249#else
250 // proj = basis projected onto plane perpendicular to v0
251 const Vec3Pack proj = (axis - inOutV0 * DotProduct(inOutV0, axis)).Normalize();
252
253 // Rotate v0 by t * 180 degrees around the chosen axis.
254 // This is equivalent to a half-rotation of a quaternion.
255 const FloatType angle = t * simd::c_pi();
256
257 FloatType sinAngle;
258 FloatType oppositeCaseTermA; // cosAngle
260#endif
262
263 // Case 3: General case (vectors are neither very close nor opposite)
264 // --------------------------------------------------------------
265#if 0
266 const FloatType theta = ::JPL::acos(dot); // Angle between vectors
267 const FloatType invSinTheta = one / ::JPL::sin(theta);
268 const FloatType w0 = ::JPL::sin((one - t) * theta) * invSinTheta;
269 const FloatType w1 = ::JPL::sin(t * theta) * invSinTheta;
270#else
271 const FloatType invSinTheta = Math::InvSqrtFast(one - dot * dot); //? May be close to zero here, but we'll select parallelCase
272 const FloatType theta = ::JPL::acos(dot);
273 FloatType sinT, cosT;
275 const FloatType w1 = sinT * invSinTheta;
276 const FloatType w0 = cosT - dot * w1;
277#endif
278 // --------------------------------------------------------------
279
280#if 1
281 // Combine all three cases into single FMA
282 inOutV0 = FMA(
284 (V1 - inOutV0),
285 inOutV0),
287 t,
290 inOutV0,
292#else
293 // Use linear interpolation for parallelCase
294 const Vec3Pack parallelCase = FMA((V1 - inOutV0), t, inOutV0);// Lerp(inOutV0, V1, t);
295
296 inOutV0 = FMA(
297 inOutV0
300 );
301
303#endif
304
305 // Normalize once at the end
306 inOutV0.Normalize();
307 }
308
309} // namespace JPL::Math
Definition Vec3Math.h:34
std::remove_cvref_t< decltype(GetX(std::declval< Vec3 >()))> FloatOf
Definition Vec3Math.h:36
Definition Math.h:61
void CreateOrthonormalBasis(const Vec3 &normal, Vec3 &tangent, Vec3 &bitangent) noexcept
Definition Vec3Math.h:84
JPL_INLINE Vec3 Nlerp(const Vec3 &v0, const Vec3 &v1, float t) noexcept
Definition Vec3Math.h:130
JPL_INLINE constexpr T FMA(T a, T b, T c) noexcept
Inlined fuse multiply-add. Compiler in some circumstances is more eager to optimize this than std::fm...
Definition Math.h:186
JPL_INLINE bool HasNans(const Vec3 &vec)
Definition Vec3Math.h:59
JPL_INLINE constexpr T Lerp(const T &v0, const T &v1, T t) noexcept
Linearly interpolate v0 towards v1.
Definition Math.h:172
JPL_INLINE constexpr T Sqrt(T x) noexcept
Definition Math.h:269
JPL_INLINE constexpr bool IsNearlyZero(T value, T errorTolerance=JPL_FLOAT_EPS_V< T >) noexcept
Definition Math.h:146
JPL_INLINE Vec3 RotateVector(const Vec3 &vector, const Vec3 &rotationAxis, float angleRad) noexcept
Definition Vec3Math.h:107
JPL_INLINE constexpr bool IsNearlyEqual(T a, T b, T tolerance=JPL_FLOAT_EPS_V< T >) noexcept
Definition Math.h:152
JPL_INLINE constexpr auto Abs(const T &value) noexcept
Standard abs is not constexpr in C++20.
Definition Math.h:87
JPL_INLINE simd InvSqrtFast(const simd &vec) noexcept
Definition SIMD.h:1783
JPL_INLINE std::pair< T, T > SinCos(T value) noexcept
Definition Math.h:164
Vec3 GetNormalizedPerpendicular(const Vec3 &vec) noexcept
Definition Vec3Math.h:67
Vec3 Slerp(const Vec3 &v0, const Vec3 &v1, float t) noexcept
Input vectors must be normalized.
Definition Vec3Math.h:142
JPL_INLINE simd clamp(const simd &value, const simd &minV, const simd &maxV) noexcept
Element-wise clamp.
Definition SIMD.h:1838
JPL_INLINE auto GetX(const Vec3Type &v) noexcept
Definition Vec3Traits.h:35
simd acos(const simd &in)
Definition SIMDMath.h:710
JPL_INLINE auto GetZ(const Vec3Type &v) noexcept
Definition Vec3Traits.h:37
JPL_INLINE auto GetY(const Vec3Type &v) noexcept
Definition Vec3Traits.h:36
JPL_INLINE simd min(const simd &a, const simd &b) noexcept
Element-wise min.
Definition SIMD.h:1813
JPL_INLINE simd abs(const simd &vec) noexcept
Definition SIMD.h:1827
simd sin(simd x) noexcept
Definition SIMDMath.h:540
Minimal Vec3 class for SIMD operations on 4 Vec3 at a time.
Definition Vec3Pack.h:41
static JPL_INLINE Vec3Pack select(const simd_mask &conditionMask, const Vec3Pack &ifTrue, const Vec3Pack &ifFalse)
Definition Vec3Pack.h:97
Definition SIMD.h:207
Minimal 4-wide 32-bit float vector implementation for SIMD.
Definition SIMD.h:60
static JPL_INLINE simd c_0() noexcept
Frequently used constants.
Definition SIMD.h:456
static JPL_INLINE simd c_1() noexcept
Definition SIMD.h:461
static JPL_INLINE simd c_pi() noexcept
Definition SIMD.h:471
static JPL_INLINE simd select(const simd_mask &mask, const simd &a, const simd &b) noexcept
Component-wise select, returns 'a' if mask is true, 'b' otherwise.
Definition SIMD.h:1034