JPL Spatial
Sound spatialization and propagation library
Loading...
Searching...
No Matches
Math.h
Go to the documentation of this file.
1//
2// ██╗██████╗ ██╗ ██╗██████╗ ███████╗
3// ██║██╔══██╗ ██║ ██║██╔══██╗██╔════╝ ** JPLSpatial **
4// ██║██████╔╝ ██║ ██║██████╔╝███████╗
5// ██ ██║██╔═══╝ ██║ ██║██╔══██╗╚════██║ https://github.com/Jaytheway/JPLSpatial
6// ╚█████╔╝██║ ███████╗██║██████╔╝███████║
7// ╚════╝ ╚═╝ ╚══════╝╚═╝╚═════╝ ╚══════╝
8//
9// Copyright 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
25
26#include <functional>
27#include <random>
28#include <numbers>
29#include <cmath>
30#include <utility>
31#include <concepts>
32
33#define JPL_USE_WR 1
34
35//#define JPL_RAND_SEED 0x6546584802711ull // deterministic for testing
36#define JPL_RAND_SEED std::random_device{}()
37
38namespace JPL
39{
40 static constexpr float JPL_SPEED_OF_SOUND = 343.0f;
41 static constexpr float JPL_INV_SPEAD_OF_SOUND = 1.0f / JPL_SPEED_OF_SOUND;
42} // namespace JPL
43
44namespace JPL::Math
45{
46 namespace InternalUtils
47 {
48 template<std::floating_point FloatType>
49 static constexpr auto four_pi = FloatType(4.0) * std::numbers::pi_v<FloatType>;
50 template<std::floating_point FloatType>
51 static constexpr FloatType two_pi = FloatType(2.0) * std::numbers::pi_v<FloatType>;
52
53 template<std::floating_point FloatType>
54 static inline FloatType RandFloat()
55 {
56 static constexpr auto bits = std::same_as<FloatType, double> ? 53u : 24u;
57 static Rand mt(JPL_RAND_SEED);
58 return std::generate_canonical<FloatType, bits>(mt);
59 }
60
61 template<class Vec3>
62 static inline Vec3 RandDirection()
63 {
64 return Normalized(Vec3{
65 RandFloat<Internal::FloatOf<Vec3>>() * 2.0f - 1.0f,
66 RandFloat<Internal::FloatOf<Vec3>>() * 2.0f - 1.0f,
68 });
69 }
70 }
71
72 template<class Vec3>
73 static inline Vec3 GetImageSource(const Vec3& point, const Vec3& planeNormal, float planeConstant)
74 {
75 const float signedDistance = DotProduct(point, planeNormal) + planeConstant;
76 return point - (planeNormal * (2.0f * signedDistance));
77 }
78
79 template<class Vec3>
80 static inline Vec3 GetImageSource(const Vec3& point, const Vec3& planeNormal, const Vec3& pointOnPlane)
81 {
82 const float planeConstant = DotProduct(-planeNormal, pointOnPlane);
83 return GetImageSource(point, planeNormal, planeConstant);
84 }
85
86 // Geometry term represents energy dispersion and occlusion during the propagation,
87 // combined with propagation operator (inverse square law + optional air absorption factor)
88 template<class Vec3>
89 static auto GeometryTerm(const Vec3& x, const Vec3& xNorm, const Vec3& y, const Vec3& yNorm) -> Internal::FloatOf<Vec3>
90 {
91 using FloatType = Internal::FloatOf<Vec3>;
92
93 Vec3 dir = y - x;
94 const FloatType dist2 = LengthSquared(dir);
95 if (dist2 < FloatType(1e-6))
96 return FloatType(0.0);
97
98 dir /= std::sqrt(dist2);
99 const FloatType cosThetaX = std::abs(DotProduct(xNorm, dir));
100 const FloatType cosThetaY = std::abs(DotProduct(yNorm, -dir));
101
102 // (optionally) here we can multiply by air absorption factor between x and y
103 return (cosThetaX * cosThetaY) / dist2; // * airAbsorption;
104 }
105
106 // Geometry term between two points (without surface normal information)
107 // is propagation operator (inverse square law + optional air absorption factor)
108 template<class Vec3>
109 static auto GeometryTerm(const Vec3& x, const Vec3& y) -> Internal::FloatOf<Vec3>
110 {
111 using FloatType = Internal::FloatOf<Vec3>;
112
113 const FloatType dist2 = LengthSquared(y - x);
114 if (dist2 < FloatType(1e-6))
115 return FloatType(0.0);
116
117 // (optionally) here we can multiply by air absorption factor between x and y
118 return 1.0f / dist2; // * airAbsorption;
119 }
120
121 template<class Vec3>
122 static auto DistanceAttenuation(const Vec3& x, const Vec3& y) -> Internal::FloatOf<Vec3>
123 {
124 return GeometryTerm(x, y);
125 }
126
127 // Calculates the specular reflection of an incident vector relative to a surface normal.
128 // @param incident Incident vector (any length).
129 // @param normal Surface normal (must be normalized).
130 // @returns Reflected vector in the specular direction, with the same length as the incident vector.
131 //
132 // Note: If 'incident' is also normalized, the result will be normalized.
133 template<class Vec3>
134 static Vec3 SpecularReflection(const Vec3& incident, const Vec3& normal)
135 {
136 return incident - Internal::FloatOf<Vec3>(2.0) * DotProduct(incident, normal) * normal;
137 }
138
139 // Cosine-weighted scattered direction in the same hemisphere (Lambert)
140 template<class Vec3>
141 static Vec3 SampleHemisphereCosine(const Vec3& normal)
142 {
143 using FloatType = Internal::FloatOf<Vec3>;
144
145 // 1. Sample in tangent space
146 const FloatType u1 = InternalUtils::RandFloat<FloatType>(); // [0,1)
147 const FloatType u2 = InternalUtils::RandFloat<FloatType>(); // [0,1)
148 const FloatType r = std::sqrt(u1); // radius in the disk
149 const FloatType phi = InternalUtils::two_pi<FloatType> *u2;
150
151 const FloatType x = r * std::cos(phi);
152 const FloatType z = r * std::sin(phi);
153 const FloatType y = std::sqrt(std::max(FloatType(0.0), FloatType(1.0) - u1)); // Y is up (height)
154
155 // 2. Orthonormal basis for the normal
156 Vec3 tangent, bitangent;
158
159 // 3. Transform to world space: [t n b] * local coords
160 return Normalized(Vec3(
161 x * tangent +
162 y * normal +
163 z * bitangent
164 ));
165 }
166
167 // Vector Based Scattering (Christensen 2005)
168 // Produces a continuous distribution between specular and diffused vectors.
169 // @param diffusion must be in range [0, 1]
170 template<class Vec3>
171 static Vec3 VectorBasedScatter2(const Vec3& specular, const Vec3& normal, float diffusion)
172 {
173 using FloatType = Internal::FloatOf<Vec3>;
174 const Vec3 randomDirection = SampleHemisphereCosine(normal);
175 return Normalized(diffusion * randomDirection + (FloatType(1.0) - diffusion) * specular);
176 }
177
178 // Vector Based Scattering (Christensen 2005)
179 // Produces a continuous distribution between specular and diffused vectors.
180 // @param diffusion must be in range [0, 1]
181 template<class Vec3>
182 static Vec3 VectorBasedScatter(const Vec3& incident, const Vec3& normal, float diffusion)
183 {
184 const Vec3 specular = SpecularReflection(incident, normal);
185 return VectorBasedScatter2(specular, normal, diffusion);
186 }
187
188 template<class Vec3>
190 {
192 float PDF;
194 };
195
196 // Vector Based Scattering (Christensen 2005)
197 // Produces a discrete distribution between specular and diffused vectors,
198 // which allows to also extract PDF (Probability Density Function)
199 // suitable for BDTP.
200 //
201 // @param diffusion (d) must be in range [0, 1]
202 //
203 // @returns with probability (1-d): deterministic specular direction;
204 //
205 // with probability d: a cosine-weighted sample
206 template<class Vec3>
207 static VBSSample<Vec3> VectorBasedScatterAndPDF(const Vec3& incident,
208 const Vec3& normal,
209 float diffusion)
210 {
211 using FloatType = Internal::FloatOf<Vec3>;
212
213 if (InternalUtils::RandFloat<FloatType>() < diffusion)
214 {
215 // --- diffuse branch
216 const Vec3 outDirection = SampleHemisphereCosine(normal);
217 return {
218 .OutDirection = outDirection,
219 .PDF = diffusion *
220 std::max(FloatType(0.0), DotProduct(normal, outDirection)) *
221 std::numbers::inv_pi_v<FloatType>,
222 .bIsSpecular = false
223 };
224 }
225 else
226 {
227 // --- specular branch
228 return {
229 .OutDirection = SpecularReflection(incident, normal),
230 .PDF = FloatType(1.0) - diffusion, // dirac weight
231 .bIsSpecular = true
232 };
233 }
234 }
235
236 template<class Vec3>
237 JPL_INLINE float ComputePDF(const Vec3& normal, const Vec3& outDirection, float diffusion, bool bIsSpecular)
238 {
239 return bIsSpecular
240 ? 1.0f - diffusion
241 : diffusion * std::max(0.0f, static_cast<float>(DotProduct(normal, outDirection))) * JPL_INV_PI;
242 }
243
244 // Acoustic Biderectional Reflectance Distribution Function (Durany et. al, 2015)
245 // @param cosineAngle cosine of the angle between outgoing and specular vectors
246 // @param diffusion diffusion or scattering coefficient must be in range [0, 1]
247 //
248 // @returns probability of reflection in the outgoing direction
249 template<std::floating_point FloatType>
250 static auto ABRDF(FloatType cosineAngle, float diffusion) -> FloatType
251 {
252 // Compute gamma (cosine between specular and outgoing)
253 const FloatType gamma = cosineAngle;
254
255 static constexpr auto epsilon =
256 std::numeric_limits<FloatType>::epsilon() * FloatType(8.0);
257
258 if (diffusion <= epsilon)
259 {
260 // When diffusion -> 0, only outgoing directions
261 // close to specularity are attainable.
262 return FloatType(std::fabs(gamma - FloatType(1.0)) <= epsilon);
263 }
264
265 // Evaluate A-BRDF according to diffusion
266 const FloatType d = diffusion;
267 const FloatType oneMinusD = FloatType(1.0) - d;
268
269 // Common sub-expressions
270 const FloatType a2 = oneMinusD * oneMinusD; // (1-d)^2
271 const FloatType gamma2 = gamma * gamma; // gamma^2
272
273 // Domain test Eq. (20)
274 const FloatType rootArg = a2 * gamma2 + FloatType(2.0) * d - FloatType(1.0);
275
276 // ..if it's negative, no solution exists
277 // (also guard against tiny negative round-off that would blow off sqrt())
278 if (rootArg <= epsilon)
279 return 0.0f;
280
281 // Extra admissibility restriction for d < 1/2:
282 // only the forward/specular-side branch is valid.
283 if (d < FloatType(0.5))
284 {
285 const FloatType gammaMin = std::sqrt((FloatType(1.0) - FloatType(2.0) * d) / a2);
286 if (gamma < gammaMin)
287 return FloatType(0.0);
288 }
289
290 const FloatType sqrtTerm = std::sqrt(rootArg);
291 const FloatType numer = FloatType(2.0) * rootArg;
292
293 if (d >= FloatType(0.5)) // Eq. (21)
294 {
295 const FloatType denom1 = InternalUtils::four_pi<FloatType> * d * sqrtTerm;
296 const FloatType term1 = numer / denom1;
297
298 const FloatType term2 = (oneMinusD * gamma) / (InternalUtils::two_pi<FloatType> * d);
299
300 return term1 + term2;
301 }
302 else // Eq. (22)
303 {
304 const FloatType denom = InternalUtils::two_pi<FloatType> * d * sqrtTerm;
305 return numer / denom;
306 }
307 }
308
309 // Acoustic Biderectional Reflectance Distribution Function (Durany et. al, 2015)
310 // @param specular specular direction vector, must be normalized
311 // @param outgoing outgoing angle, must be normalized
312 // @param diffusion diffusion or scattering coefficient must be in range [0, 1]
313 //
314 // @returns probability of reflection in the outgoing direction
315 template<class Vec3>
316 static JPL_INLINE auto ABRDF(const Vec3& specular, const Vec3& outgoing, float diffusion) -> Internal::FloatOf<Vec3>
317 {
318 using FloatType = Internal::FloatOf<Vec3>;
319
320 // Compute gamma (cosine between specular and outgoing)
321 const FloatType gamma = DotProduct(specular, outgoing);
322 return ABRDF(gamma, diffusion);
323 }
324
325 // Acoustic Biderectional Reflectance Distribution Function (Durany et. al, 2015)
326 // @param incident incident vector, must be normalized
327 // @param normal surface normal vector, must be normalized
328 // @param outgoing outgoing angle, must be normalized
329 // @param diffusion diffusion or scattering coefficient must be in range [0, 1]
330 //
331 // @returns probability of reflection in the outgoing direction
332 template<class Vec3>
333 static JPL_INLINE auto ABRDF(const Vec3& incident, const Vec3& normal, const Vec3& outgoing, float diffusion) -> Internal::FloatOf<Vec3>
334 {
335 // Compute specular direction (assume incident, normal, outgoing are normalized)
336 const Vec3 specular = SpecularReflection(incident, normal);
337 return ABRDF(specular, outgoing, diffusion);
338 }
339
340} // namespace JPL::Math
#define JPL_RAND_SEED
Definition Math.h:36
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 float ComputePDF(const Vec3 &normal, const Vec3 &outDirection, float diffusion, bool bIsSpecular)
Definition Math.h:237
Definition AcousticMaterial.h:36
JPL_INLINE simd min(const simd &a, const simd &b) noexcept
Element-wise min.
Definition SIMD.h:1813
Definition Math.h:190
Vec3 OutDirection
Definition Math.h:191
float PDF
Definition Math.h:192
bool bIsSpecular
Definition Math.h:193
Definition Random.h:33