JPL Spatial
Sound spatialization and propagation library
Loading...
Searching...
No Matches
DirectPathService.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
22#include "JPLSpatial/Core.h"
31
32#include <algorithm>
33#include <cmath>
34#include <memory>
35#include <iterator>
36#include <vector>
37#include <memory_resource>
38
39namespace JPL
40{
41 //==========================================================================
42 template<CVec3 Vec3Type>
44 {
45 float Distance; //< Distance from source to listener
46 float DirectionDot; //< Dot product between source direction relative to listener and listener's forward vector
47 float InvDirectionDot; //< Dot product listener direction relative to source and source's forward vector
48
49 JPL::Position<Vec3Type> Position; //< Direction and orientation relative to listener
50 };
51
53 {
54 float InnerAngle; //< width of the inner sector in radians
55 float OuterAngle; //< width of the outer sector in radians, should be > InnerAngle
56 };
57
58 using AttenuationCurveRef = std::shared_ptr<AttenuationFunction>;
59
60 // TODO: we might want Curve ID to be able to retrieve volume and other attenuations quickly
66
72
75
81
82 //==========================================================================
84 {
85 public:
86 // Alias to override allocator for the internal FlatMap we use
87 template<class Key, class T>
89
90 public:
91 DirectPathService() = default;
92
95
96 // High level API to:
97 // - get distance-based volume attenuation and air absorption values
98
100
103 JPL_INLINE bool ReleaseEffectData(DirectEffectHandle source);
104
109
118 template<CVec3 Vec3Type>
120 const Position<Vec3Type>& listener);
121 template<CVec3 Vec3Type>
122 static JPL_INLINE float ProcessAngleAttenuation(const Vec3Type& position,
123 const Position<Vec3Type>& referencePoint,
124 AttenuationCone cone);
125
126 static JPL_INLINE float ProcessAngleAttenuation(float azimuth, AttenuationCone cone);
127
128 static JPL_INLINE float EvaluateDistance(float distance, const AttenuationCurveRef& attenuationCurve);
129
130 // TODO: mabye we could batch process multiple distances/sources per curve, if they are sharing curves?
131 /*
132 Essentially it is a tradeoff between:
133 - efficient evaluation - which is better if we have a lot of curves per source
134 - efficient cache look-up - which is better if we access values a lot and share curves between sources
135 */
136
145 JPL_INLINE bool EvaluateDistance(DirectEffectHandle source, float distance);
146
155 JPL_INLINE bool EvaluateDirection(DirectEffectHandle source, float directionDot);
156
157
167 JPL_INLINE float GetDistanceAttenuation(DirectEffectHandle source, const AttenuationCurveRef& curve) const;
168
176 JPL_INLINE float GetDirectionAttenuation(DirectEffectHandle source) const;
177
178 private:
179 static JPL_INLINE float ProcessAngleAttenuationImpl(float azimutCos, const AttenuationCone& cone);
180
181 private:
182 using CurveAttenuationCacheArray = std::pmr::vector<CurveAttenuationCache>;
183
184 // TODO: can we store cache for more efficient access?
187 };
188} // namespace JPL
189
190//==============================================================================
191//
192// Code beyond this point is implementation detail...
193//
194//==============================================================================
195namespace JPL
196{
198 {
199 const auto handle = DirectEffectHandle::New();
200 mAttenuationCache.emplace(handle,
201 initParameters.BaseCurve
202 ? CurveAttenuationCacheArray({ {.Curve = initParameters.BaseCurve, .AttenuationValue = 1.0f } }, GetDefaultMemoryResource())
203 : CurveAttenuationCacheArray(GetDefaultMemoryResource()));
204
205 mDirectionAttenuationCache.emplace(handle,
206 ConeAttenuationCache{ .Cone = initParameters.AttenuationCone, .AttenuationValue = 1.0f });
207
208 return handle;
209 }
210
212 {
213 return mAttenuationCache.erase(source) + mDirectionAttenuationCache.erase(source);
214 }
215
217 {
218 if (!source.IsValid() || !attenuationFunction)
219 return nullptr;
220
221 //std::shared_ptr<AttenuationFunction> curve = make_pmr_shared(attenuationFunction);
222 auto& cache = mAttenuationCache[source];
223 return cache.emplace_back(attenuationFunction, 1.0f).Curve;
224 //return curve;
225 }
226
228 {
229 auto it = mAttenuationCache.find(source);
230 if (it == mAttenuationCache.end())
231 return 1.0f;
232
233 auto cache = std::ranges::find(it->second, curve, [](const CurveAttenuationCache& cache) { return cache.Curve; });
234 if (cache != std::ranges::end(it->second))
235 return cache->AttenuationValue;
236
237 return 1.0f;
238 }
239
241 {
242 auto it = mDirectionAttenuationCache.find(source);
243 if (it == mDirectionAttenuationCache.end())
244 return 1.0f;
245
246 return it->second.AttenuationValue;
247 }
248
249 JPL_INLINE float DirectPathService::EvaluateDistance(float distance, const AttenuationCurveRef& attenuationCurve)
250 {
251 return attenuationCurve->Evaluate(distance);
252 }
253
254 JPL_INLINE bool DirectPathService::EvaluateDistance(DirectEffectHandle source, float distance)
255 {
256 auto it = mAttenuationCache.find(source);
257 if (it == mAttenuationCache.end())
258 return false;
259
260 for (CurveAttenuationCache& cache : it->second)
261 cache.AttenuationValue = cache.Curve->Evaluate(distance);
262
263 return true;
264 }
265
266 JPL_INLINE bool DirectPathService::EvaluateDirection(DirectEffectHandle source, float directionDot)
267 {
268 auto it = mDirectionAttenuationCache.find(source);
269 if (it == mDirectionAttenuationCache.end())
270 return false;
271
272 JPL_ASSERT(directionDot >= -1.0f && directionDot <= 1.0f);
273
274 it->second.AttenuationValue = ProcessAngleAttenuationImpl(directionDot, it->second.Cone);
275
276 return true;
277 }
278
279 template<CVec3 Vec3Type>
281 {
282 static const Vec3Type cForwardAxis(0, 0, -1); // TODO: this is very assuming
283#if 1
284 const Basis<Vec3Type> listenerBasis = listener.Orientation.ToBasisUnsafe();
285 const Vec3Type sourceRelativePosition = source.Location - listener.Location;
286
287 Vec3Type sourcePosInListenerFrame = listenerBasis.InverseTransform(sourceRelativePosition);
288 // If source is directly on top, above or below the listener,
289 // nudge it a bit forward
291 if (Math::IsNearlyZero(GetX(sourcePosInListenerFrame)) && Math::IsNearlyZero(GetZ(sourcePosInListenerFrame)))
292 {
293 sourcePosInListenerFrame += cForwardAxis * 1e-5f;
294 }
295
296 // Get distance and cos of source in listener's frame
297 const float distance = Length(sourcePosInListenerFrame);
298 const Vec3Type dirRelativeToListener = sourcePosInListenerFrame / distance;
299 const float directionDot = DotProduct(dirRelativeToListener, cForwardAxis);
300
301 // Get cos of listener in source's frame
302 const Vec3Type& sourceForward = source.Orientation.Forward;
303 const Vec3Type listenerToSourceDir = listenerBasis.InverseTransform(-dirRelativeToListener);
304 const float invDirectionDot = DotProduct(sourceForward, listenerToSourceDir);
305
306 // Get orientation of source in listener's frame
307 const Basis<Vec3Type> sourceToListenerOrientation = listenerBasis.InverseTransform(source.Orientation.ToBasisUnsafe());
308#else
309 Vec3Type sourcePosInListenerFrame = listener.Orientation.ToQuat().Rotate(source.Location);
310
311 // If source is directly on top, above or below the listener,
312 // nudge it a bit forward
314 if (Math::IsNearlyZero(GetX(sourcePosInListenerFrame)) && Math::IsNearlyZero(GetZ(sourcePosInListenerFrame)))
315 {
316 sourcePosInListenerFrame += cForwardAxis * 1e-5f;
317 }
318
319 // Get distance and cos of source in listener's frame
320 const float distance = Length(sourcePosInListenerFrame);
321 const Vec3Type dirRelativeToListener = sourcePosInListenerFrame / distance;
322 const float directionDot = DotProduct(dirRelativeToListener, cForwardAxis);
323
324 // Get cos of listener in source's frame
325 const Vec3Type sourceForward = source.Orientation.ToQuat().Rotate(cForwardAxis);
326 const Vec3Type listenerToSourceDir = listener.Orientation.ToQuat().Conjugated().Rotate(-dirRelativeToListener);
327 const float invDirectionDot = DotProduct(sourceForward, listenerToSourceDir);
328
329 // Get orientation of source in listener's frame
330 const Basis<Vec3Type> sourceToListenerOrientation =
331 (listener.Orientation.ToQuat().Conjugated() * source.Orientation.ToQuat()).ToBasis();
332#endif
334 .Distance = distance,
335 .DirectionDot = directionDot,
336 .InvDirectionDot = invDirectionDot,
337 .Position = {
338 .Location = dirRelativeToListener,
339 .Orientation = {.Up = sourceToListenerOrientation.Y, .Forward = sourceToListenerOrientation.Z}
340 }
341 };
342 }
343
344 JPL_INLINE float DirectPathService::ProcessAngleAttenuationImpl(float azimutCos, const AttenuationCone& cone)
345 {
346 // Compute cosines of half of the cone sectors
347 const float cutoffInner = std::cos(cone.InnerAngle * 0.5f); // TODO: can we cache actual dot isntead of angles?
348 const float cutoffOuter = std::cos(cone.OuterAngle * 0.5f);
349
350 float factor = 0.0f;
351
352 if (azimutCos > cutoffInner)
353 return factor;
354
355 if (azimutCos > cutoffOuter)
356 {
357 // Between inner and outer cones
358 factor = (cutoffInner - azimutCos) / (cutoffInner - cutoffOuter);
359 }
360 else
361 {
362 // Outside the outer cone
363 factor = 1.0f;
364 }
365
366 return factor;
367 }
368
370 {
371 if (cone.InnerAngle >= JPL_TWO_PI)
372 {
373 // Inner angle is 360 degrees so no need to do any attenuation.
374 return 0.0f;
375 }
376
377 return ProcessAngleAttenuationImpl(std::cos(azimuth), cone);
378 }
379
380 template<CVec3 Vec3Type>
381 JPL_INLINE float DirectPathService::ProcessAngleAttenuation(const Vec3Type& position,
382 const Position<Vec3Type>& referencePoint,
383 AttenuationCone cone)
384 {
385 if (cone.InnerAngle >= JPL_TWO_PI)
386 {
387 // Inner angle is 360 degrees so no need to do any attenuation.
388 return 0.0f;
389 }
390
391 // Position and reference must not be the same point
392 JPL_ASSERT(!Math::IsNearlyEqual(position, referencePoint.Location));
393
394 const Vec3Type referenceForward = referencePoint.Orientation.Forward;
395 const Vec3Type sourceDirection = Normalized(position - referencePoint.Location);
396 const float dot = DotProduct(referenceForward, sourceDirection);
397
398 return ProcessAngleAttenuationImpl(dot, cone);
399 }
400
401} // namespace JPL
#define JPL_ASSERT(inExpression,...)
Main assert macro, usage: JPL_ASSERT(condition, message) or JPL_ASSERT(condition)
Definition ErrorReporting.h:80
Definition DirectPathService.h:84
JPL_INLINE bool ReleaseEffectData(DirectEffectHandle source)
Definition DirectPathService.h:211
JPL_INLINE float GetDistanceAttenuation(DirectEffectHandle source, const AttenuationCurveRef &curve) const
Definition DirectPathService.h:227
DirectPathService(const DirectPathService &)=delete
DirectPathService & operator=(const DirectPathService &)=delete
FlatMapWithAllocator< Key, T, std::pmr::polymorphic_allocator > FlatMapType
Definition DirectPathService.h:88
static JPL_INLINE float ProcessAngleAttenuation(const Vec3Type &position, const Position< Vec3Type > &referencePoint, AttenuationCone cone)
Definition DirectPathService.h:381
static JPL_INLINE DirectPathResult< Vec3Type > ProcessDirectPath(const Position< Vec3Type > &source, const Position< Vec3Type > &listener)
Definition DirectPathService.h:280
JPL_INLINE AttenuationCurveRef AssignAttenuationCurve(DirectEffectHandle source, AttenuationCurveRef attenuationFunction)
Definition DirectPathService.h:216
JPL_INLINE float GetDirectionAttenuation(DirectEffectHandle source) const
Definition DirectPathService.h:240
JPL_INLINE bool EvaluateDirection(DirectEffectHandle source, float directionDot)
Definition DirectPathService.h:266
JPL_INLINE DirectEffectHandle InitializeDirrectEffect(const DirectEffectInitParameters &initParameters)
Definition DirectPathService.h:197
static JPL_INLINE float EvaluateDistance(float distance, const AttenuationCurveRef &attenuationCurve)
Definition DirectPathService.h:249
Definition FlatMap.h:51
JPL_INLINE constexpr bool IsNearlyZero(T value, T errorTolerance=JPL_FLOAT_EPS_V< T >) noexcept
Definition Math.h:146
JPL_INLINE constexpr bool IsNearlyEqual(T a, T b, T tolerance=JPL_FLOAT_EPS_V< T >) noexcept
Definition Math.h:152
Definition AcousticMaterial.h:36
JPL_INLINE auto GetX(const Vec3Type &v) noexcept
Definition Vec3Traits.h:35
std::shared_ptr< AttenuationFunction > AttenuationCurveRef
Definition DirectPathService.h:58
JPL_INLINE auto GetZ(const Vec3Type &v) noexcept
Definition Vec3Traits.h:37
std::pmr::memory_resource * GetDefaultMemoryResource() noexcept
Definition Memory.h:42
Definition DirectPathService.h:53
float OuterAngle
Definition DirectPathService.h:55
float InnerAngle
Definition DirectPathService.h:54
Orthonormal basis (column-major)
Definition MinimalBasis.h:35
JPL_INLINE Vec3 InverseTransform(const Vec3 &pWorld) const noexcept
Apply rotation world -> local.
Definition MinimalBasis.h:99
Vec3 Z
Definition MinimalBasis.h:38
Vec3 Y
Definition MinimalBasis.h:38
Definition DirectPathService.h:68
float AttenuationValue
Definition DirectPathService.h:70
AttenuationCone Cone
Definition DirectPathService.h:69
Definition DirectPathService.h:62
float AttenuationValue
Definition DirectPathService.h:64
AttenuationCurveRef Curve
Definition DirectPathService.h:63
Definition DirectPathService.h:77
JPL::AttenuationCone AttenuationCone
Definition DirectPathService.h:79
AttenuationCurveRef BaseCurve
Definition DirectPathService.h:78
Definition DirectPathService.h:44
float DirectionDot
Definition DirectPathService.h:46
float InvDirectionDot
Definition DirectPathService.h:47
float Distance
Definition DirectPathService.h:45
JPL::Position< Vec3Type > Position
Definition DirectPathService.h:49
Definition DirectPathService.h:73
static constexpr IDType New() noexcept
Definition IDType.h:44
constexpr bool IsValid() const noexcept
Definition IDType.h:46
Location and orientation in one place.
Definition Position.h:80
OrientationData< Vec3Type > Orientation
Definition Position.h:82
Vec3Type Location
Definition Position.h:81