JPL Spatial
Sound spatialization and propagation library
Loading...
Searching...
No Matches
SpecularRayTracing.h
Go to the documentation of this file.
1//
2// ██╗██████╗ ██╗ ██╗██████╗ ███████╗
3// ██║██╔══██╗ ██║ ██║██╔══██╗██╔════╝ ** JPLSpatial **
4// ██║██████╔╝ ██║ ██║██████╔╝███████╗
5// ██ ██║██╔═══╝ ██║ ██║██╔══██╗╚════██║ https://github.com/Jaytheway/JPLSpatial
6// ╚█████╔╝██║ ███████╗██║██████╔╝███████║
7// ╚════╝ ╚═╝ ╚══════╝╚═╝╚═════╝ ╚══════╝
8//
9// Copyright 2026 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 <concepts>
34#include <memory_resource>
35#include <span>
36#include <ranges>
37#include <vector>
38#include <utility>
39
40//? temp. move somewhere reasonable
41#ifndef JPL_PROFILE
42 #define JPL_PROFILE(...)
43#endif // !JPL_PROFILE
44
45#ifndef JPL_PROFILE_SET_INT
46 #define JPL_PROFILE_SET_INT(Stat, Value)
47#endif // !JPL_PROFILE_SET_INT
48
49#ifndef JPL_PROFILE_SET_FLOAT
50 #define JPL_PROFILE_SET_FLOAT(Stat, Value)
51#endif // !JPL_PROFILE_SET_FLOAT
52
53
54#ifndef JPL_HAS_FUNCTION
55#define JPL_HAS_FUNCTION(Type, MemberFunctionCallPrototype)\
56requires(Type Object){ Object.MemberFunctionCallPrototype; }
57#endif
58
59#ifndef JPL_HAS_FUNCTION_R
60#define JPL_HAS_FUNCTION_R(Type, MemberFunctionCallPrototype, ReturnType)\
61requires(Type Object) { { Object.MemberFunctionCallPrototype } -> std::same_as<ReturnType>; }
62#endif
63
64namespace JPL
65{
66 //==========================================================================
67 enum class ETraceDirection
68 {
69 Forward,
71 };
72
80
82 template<class IntersectionType>
84 {
85 // Trace hit data for this node
86 IntersectionType Hit;
87
88 // Surface hash up to this node, or just this node (depends on context)
90 };
91
93 template<class IntersectionType>
94 struct alignas(JPL_CACHE_LINE_SIZE) TracedPath
95 {
96 std::pmr::vector<TraceNode<IntersectionType>> Nodes;
97 };
98
100 template<class IntersectionType>
102 {
103 std::pmr::vector<TracedPath<IntersectionType>> Paths;
104 };
105
106 //==========================================================================
140 {
141 public:
142 static constexpr std::size_t cMaxOrder = 16; // arbitrary number
143
144 //==========================================================================
151 template<class SceneType, bool bPathHashCombine = true>
152 static void Trace(const SceneType& sceneInterface,
153 const typename SceneType::Vec3& origin,
154 const TraceParameters& parameters,
156
157 //==========================================================================
169 template<class SceneType, class SpecularPathCacheContainer>
170 static void ProcessTraces(SceneType& sceneInterface,
171 const typename SceneType::SourceData& sourceData,
173 std::span<const typename SceneType::ReceiverData> receiverData,
174 SpecularPathCacheContainer& caches);
175
189 template<class SceneType, class SpecularPathCacheContainer>
190 static void ProcessTraces(SceneType& sceneInterface,
191 const typename SceneType::SourceData& sourceData,
193 std::span<const typename SceneType::ReceiverData> receiverData,
194 std::span<TraceResults<typename SceneType::Intersection>> receiverTraces,
195 SpecularPathCacheContainer& caches);
196
197 private:
198 //==========================================================================
199 struct TraceInfo
200 {
201 uint32 PathCount;
202 uint32 TotalNumSubpaths;
203 uint32 MaxOrder;
204 uint32 TotalNumImageSources;
205
206 template<class IntersectionType>
207 static TraceInfo Parse(const TraceResults<IntersectionType>& traces);
208 };
209
210 template<class Vec3>
211 struct ImageSourceBuffer
212 {
213 std::pmr::vector<Vec3> ImageSources;
214 std::pmr::vector<uint32> IndexTable;
215
216 static ImageSourceBuffer MakeFor(const auto& subpathsList);
217 std::span<Vec3> GetImageSourcesFor(const auto& subpath, uint32 subpathIndex);
218 };
219
220 template<class PathNodeType, class Vec3>
221 struct alignas(JPL_CACHE_LINE_SIZE) NewSubpath
222 {
223 // Set in in validation step
224 JPL::EnergyBands EnergyLoss;
225 bool bIsValid;
226
227 std::span<const PathNodeType> Subpath;
228 ETraceDirection Direction;
229 uint32 ReceiverIdx;
230 JPL::SpecularPathId PathId;
231 Vec3 LastImageSource;
232 };
233
234 //======================================================================
237 template<class SceneType, class SpecularPathCacheContainer>
238 class ProcessRoutine
239 {
240 using Vec3 = typename SceneType::Vec3;
241 using Intersection = typename SceneType::Intersection;
242 using PathNodeType = TraceNode<Intersection>;
243 using NewSubpathEntry = NewSubpath<PathNodeType, Vec3>;
244 using SourceData = typename SceneType::SourceData;
245 using ReceiverData = typename SceneType::ReceiverData;
246
247 SceneType& mSceneInterface;
248 const SourceData& mSourceData;
249 TraceResults<Intersection>& mSourceTraces;
250 std::span<const ReceiverData> mReceiverData;
251 std::span<TraceResults<Intersection>> mReceiverTraces;
252 SpecularPathCacheContainer& mCaches;
253
254 public:
255 ProcessRoutine(SceneType& sceneInterface,
256 const SourceData& sourceData,
257 TraceResults<Intersection>& sourceTraces,
258 std::span<const ReceiverData> receiverData,
259 std::span<TraceResults<Intersection>> receiverTraces,
260 SpecularPathCacheContainer& caches);
261
263 void Process();
264
265 std::pair<uint32, uint32> GetSubpathCountAndMaxOrder() const;
266
268 static TraceInfo PreprocessTraces(TraceResults<Intersection>& traces);
269
270 void CreateForwardSubpathsEntries(TraceResults<Intersection>& traces,
271 ScratchHashSetIdentity& uniqueCheckSet,
272 std::pmr::vector<NewSubpathEntry>& outNewSubpaths) const;
273
274 void CreateBackwardSubpathsEntries(std::span<TraceResults<Intersection>> traces,
275 ScratchHashSetIdentity& uniqueCheckSet,
276 std::pmr::vector<NewSubpathEntry>& outNewSubpaths) const;
277
279 template<ETraceDirection PathDirection>
280 void ConstructImageSources(const Vec3& sourcePosition,
281 std::span<const TraceNode<Intersection>> path,
282 std::span<Vec3> outImageSources) const;
283
285 template<ETraceDirection TraceDirection>
286 void ConstructImageSources(const Vec3& sourcePosition,
287 std::span<const TraceNode<Intersection>> path,
288 ETraceDirection pathDirection,
289 std::span<Vec3> outImageSources) const;
290
292 template<ETraceDirection PathDirection>
293 bool ValidatePathForListener(std::span<const TraceNode<Intersection>> nodes, // does not include source/receiver
294 std::span<const Vec3> imageSources,
295 const Vec3& listenerPosition) const;
296
298 template<ETraceDirection TraceDirection>
299 bool ValidatePathForListener(std::span<const TraceNode<Intersection>> nodes,
300 ETraceDirection pathDirection,
301 std::span<const Vec3> imageSources,
302 const Vec3& listenerPosition) const;
303
305 void ValidateNewSubpaths(std::span<NewSubpathEntry> newSubpaths,
306 const Vec3 listenerPosition) const;
307
308
310 void AccumulateMaterialAbsorption(std::span<const TraceNode<Intersection>> surfaces,
311 EnergyBands& outEnergyLoss) const;
312
314 void CacheValidatedSubpaths(std::span<NewSubpathEntry> validatedSubpaths,
315 uint32 maxPathOrderHint) const;
316 };
317 };
318
319} // namespace JPL
320
321//==============================================================================
322//
323// Code beyond this point is implementation detail...
324//
325//==============================================================================
326
327// TODO: maybe refactor the interface to use source-listener pairs (?)
328// however, multiple listeners for acoustics simulation should be discouraged.
329
330// TODO: this whole Forward/Backward semantics is a bit messy
331
332namespace JPL
333{
334 //==========================================================================
335 template<class SceneType, bool bPathHashCombine>
336 inline void SpecularRayTracing::Trace(const SceneType& sceneInterface,
337 const typename SceneType::Vec3& origin,
338 const TraceParameters& parameters,
340 {
341 using Vec3 = typename SceneType::Vec3;
342 using Ray = typename SceneType::Ray;
343 using Intersection = typename SceneType::Intersection;
344 using PathType = TracedPath<Intersection>;
345
346 outTraceResults.Paths.resize(parameters.NumPrimaryRays);
347 for (PathType& path : outTraceResults.Paths) //? not ideal, we still make 100 allocations here
348 {
349 path.Nodes.reserve(parameters.MaxTraceOrder); // The max order may be large when late reverberation is implemented
350 }
351
352 auto traceRay = [&](int32 index)
353 {
354 PathType& path = outTraceResults.Paths[index];
355
356 // Sample primary outgoing ray
357 Ray ray(origin, Math::InternalUtils::RandDirection<Vec3>());
358 uint32 hash = Hash::cStartSeed;
359
360 // Trace rays up to 'MaxOrder'
361 for (uint32 d = 0; d < parameters.MaxTraceOrder; ++d)
362 {
363 Intersection hit;
364 if (not sceneInterface.Intersect(ray, parameters.MaxRayLength, hit))
365 {
366 break;
367 }
368
369 if constexpr (bPathHashCombine)
370 {
371 HashCombine32(hash, sceneInterface.GetHash(hit));
372 }
373 else
374 {
375 // If not combining, just use the user-provided surface hash
376 hash = sceneInterface.GetHash(hit);
377 }
378
379
380 path.Nodes.push_back({ .Hit = hit, .Hash = hash });
381
382 // Small offset to avoide self intersection
383 static constexpr float offset = 0.001f;
384
385 // Generate next outgoing ray
386 ray.Origin = sceneInterface.GetPosition(hit) + sceneInterface.GetNormal(hit) * offset;
387 ray.Direction = Math::SpecularReflection(ray.Direction, sceneInterface.GetNormal(hit));
388 }
389 };
390
391 // Do the scene trace
392 {
393 // Give the caller a chance to prepare for a potentially concurrent set of traces
394 // (e.g. lock physics scene)
395 if constexpr (JPL_HAS_FUNCTION(SceneType, PreTrace()))
396 {
397 sceneInterface.PreTrace();
398 }
399
400 if constexpr (JPL_HAS_FUNCTION(SceneType, ParallelFor(uint32(42u), traceRay)))
401 {
402 sceneInterface.ParallelFor(parameters.NumPrimaryRays, traceRay);
403 }
404 else
405 {
406 for (uint32 i = 0; i < parameters.NumPrimaryRays; ++i)
407 {
408 traceRay(i);
409 }
410 }
411
412 if constexpr (JPL_HAS_FUNCTION(SceneType, PostTrace()))
413 {
414 sceneInterface.PostTrace();
415 }
416 }
417 }
418
419 //==========================================================================
420 template<class Vec3>
421 inline auto SpecularRayTracing::ImageSourceBuffer<Vec3>::MakeFor(const auto& subpathsList) -> ImageSourceBuffer<Vec3>
422 {
423 // Construct Image Source table
424 const uint32 totalNumberOfImageSources = Algo::Accumulate(subpathsList, 0u, [](uint32 acc, const auto& entry)
425 {
426 return acc + static_cast<uint32>(entry.Subpath.size() + 1); // +1 for source
427 });
428
429 ImageSourceBuffer ISBuffer
430 {
431 std::pmr::vector<Vec3>(totalNumberOfImageSources, JPL::GetDefaultMemoryResource()),
432 std::pmr::vector<uint32>(subpathsList.size(), JPL::GetDefaultMemoryResource())
433 };
434
435 for (uint32 entryIndex = 0, ISCacheOffset = 0; entryIndex < subpathsList.size(); ++entryIndex)
436 {
437 ISBuffer.IndexTable[entryIndex] = ISCacheOffset;
438 const auto& entry = subpathsList[entryIndex];
439 const uint32 imageSourcePathSize = entry.Subpath.size() + 1; // +1 for source
440 ISCacheOffset += imageSourcePathSize;
441
442 JPL_ASSERT(ISCacheOffset <= ISBuffer.ImageSources.size());
443 }
444
445 return ISBuffer;
446 }
447
448 template<class Vec3>
449 inline std::span<Vec3> SpecularRayTracing::ImageSourceBuffer<Vec3>::GetImageSourcesFor(const auto& subpath, uint32 subpathIndex)
450 {
451 const uint32 imageSourcePathSize = subpath.size() + 1; // +1 for source
452 return std::span<Vec3>(&ImageSources[IndexTable[subpathIndex]], imageSourcePathSize);
453 }
454
455 //==========================================================================
456 template<class IntersectionType>
457 auto SpecularRayTracing::TraceInfo::Parse(const TraceResults<IntersectionType>& traces) -> TraceInfo
458 {
459 TraceInfo info;
460 info.PathCount = static_cast<uint32>(traces.Paths.size());
461
462 info.TotalNumSubpaths = 0;
463 info.MaxOrder = 0;
464 for (const auto& path : traces.Paths)
465 {
466 info.TotalNumSubpaths += path.Nodes.size();
467 info.MaxOrder = std::max(info.MaxOrder, static_cast<uint32>(path.Nodes.size()));
468 }
469
470 info.TotalNumImageSources = info.TotalNumSubpaths + info.PathCount; // +1 for source per path
471
472 return info;
473 }
474
475 //==========================================================================
476 template<class SceneType, class SpecularPathCacheContainer>
477 inline void SpecularRayTracing::ProcessTraces(SceneType& sceneInterface,
478 const typename SceneType::SourceData& sourceData,
480 std::span<const typename SceneType::ReceiverData> receiverData,
481 SpecularPathCacheContainer& caches)
482 {
483 JPL_PROFILE(SpecularRayTracing_ProcessTraces);
484
485 ProcessRoutine(sceneInterface, sourceData, traces, receiverData, {}, caches).Process();
486 }
487
488 //==========================================================================
489 template<class SceneType, class SpecularPathCacheContainer>
490 inline void SpecularRayTracing::ProcessTraces(SceneType& sceneInterface,
491 const typename SceneType::SourceData& sourceData,
493 std::span<const typename SceneType::ReceiverData> receiverData,
494 std::span<TraceResults<typename SceneType::Intersection>> receiverTraces,
495 SpecularPathCacheContainer& caches)
496 {
497 JPL_PROFILE(SpecularRayTracing_ProcessTraces);
498
499 ProcessRoutine(sceneInterface, sourceData, sourceTraces, receiverData, receiverTraces, caches).Process();
500 }
501
502 //==========================================================================
503 template<class SceneType, class SpecularPathCacheContainer>
504 inline SpecularRayTracing::ProcessRoutine<SceneType, SpecularPathCacheContainer>::
505 ProcessRoutine(SceneType& sceneInterface,
506 const SourceData& sourceData,
507 TraceResults<Intersection>& sourceTraces,
508 std::span<const ReceiverData> receiverData,
509 std::span<TraceResults<Intersection>> receiverTraces,
510 SpecularPathCacheContainer& caches)
511 : mSceneInterface(sceneInterface)
512 , mSourceData(sourceData)
513 , mSourceTraces(sourceTraces)
514 , mReceiverData(receiverData)
515 , mReceiverTraces(receiverTraces)
516 , mCaches(caches)
517 {
518 // TODO: stack allocator to reuse memory throughout the routine stages (?)
519 }
520
521 template<class SceneType, class SpecularPathCacheContainer>
522 inline void SpecularRayTracing::ProcessRoutine<SceneType, SpecularPathCacheContainer>::Process()
523 {
524 // 1. Preprocess traces
525 const auto [totalSubpathCount, maxPathOrder] = GetSubpathCountAndMaxOrder();
526
527 // Paths that don't exist yet in the Specular Cache
528 // and need to be validated and added to it.
529 std::pmr::vector<NewSubpathEntry> newSubpaths(JPL::GetDefaultMemoryResource());
531 // TODO: this could potentially be huge (50 rays * 3 depth * 10 sources * 64 bytes = 96k bytes)
532 // if this does turn out to be huge, we could do our validation below per receiver and move this array to per receiver loop as well
533
534 // 2. Create unique subpaths
535 {
537
538 // Using memory resource as a simple RAII
540 std::pmr::monotonic_buffer_resource resource(bufferSize, JPL::GetDefaultMemoryResource());
542
543 CreateForwardSubpathsEntries(mSourceTraces, subpathsChecked, newSubpaths);
544
545 if (not mReceiverTraces.empty())
546 {
547 CreateBackwardSubpathsEntries(mReceiverTraces, subpathsChecked, newSubpaths);
548 }
549 }
550
551 // 3. Validate specular reflections for the unique subpaths
552 ValidateNewSubpaths(std::span(newSubpaths), mSourceData.Position); //? allocating
553
554 // 4. Write validated specular reflection paths to cache
555 CacheValidatedSubpaths(std::span(newSubpaths), maxPathOrder); //? allocating
556 }
557
558 template<class SceneType, class SpecularPathCacheContainer>
559 inline std::pair<uint32, uint32> SpecularRayTracing::ProcessRoutine<SceneType, SpecularPathCacheContainer>::GetSubpathCountAndMaxOrder() const
560 {
562
563 const TraceInfo sourceTraceInfo = PreprocessTraces(mSourceTraces);
564 const uint32 listenerToReceiverSubpathCount = sourceTraceInfo.TotalNumSubpaths * mReceiverData.size();
565
568
569 for (auto& receiverTraceResults : mReceiverTraces)
570 {
571 const TraceInfo receiverTraceInfo = PreprocessTraces(receiverTraceResults);
572 totalSubpaths += receiverTraceInfo.TotalNumSubpaths;
573 maxPathOrder = std::max(maxPathOrder, receiverTraceInfo.MaxOrder);
574 }
575
576 return std::pair(totalSubpaths, maxPathOrder);
577
578 }
579
580 //==========================================================================
581 template<class SceneType, class SpecularPathCacheContainer>
582 inline auto SpecularRayTracing::ProcessRoutine<SceneType, SpecularPathCacheContainer>::PreprocessTraces(TraceResults<Intersection>& traces) -> TraceInfo
583 {
584 // Remove empty paths
585 std::erase_if(traces.Paths, [](const auto& path) { return path.Nodes.empty(); });
586
587#if 0
588 // Second, remove duplicate surface paths
589 {
590 // Use preallocated growing buffer to avoid micro-allocations
592 void* buffer = JPL::GetDefaultMemoryResource()->allocate(memSize);
593
594 auto eraseDuplicatePaths = [&](auto& paths)
595 {
597
600 std::erase_if(paths, [&uniqueKeys](const auto& path)
601 {
602 return not uniqueKeys.Insert(path.Nodes.back().Hash);
603 });
604 };
605
607
609 }
610#endif
611
612 // Parse trace info
613 return TraceInfo::Parse(traces);
614 }
615
616 //==========================================================================
617 template<class SceneType, class SpecularPathCacheContainer>
618 inline void SpecularRayTracing::ProcessRoutine<SceneType, SpecularPathCacheContainer>::CreateForwardSubpathsEntries(TraceResults<Intersection>& traces,
620 std::pmr::vector<NewSubpathEntry>& outNewSubpaths) const
621 {
622 using NewSubpathEntry = NewSubpath<PathNodeType, Vec3>;
623
624 for (uint32 pathIdx = 0; pathIdx < traces.Paths.size(); ++pathIdx)
625 {
626 const auto& path = traces.Paths[pathIdx].Nodes;
627
628 for (uint32 nf = 1; nf <= path.size(); ++nf) // For each Subpath in Path
629 {
630 std::span<const PathNodeType> subpath(path.data(), nf);
631
632 JPL::SpecularPathId pathPartialId{ .Id = mSourceData.Id };
633 pathPartialId.AddVertex(subpath.back().Hash);
634
635 for (uint32 receiverIdx = 0; receiverIdx < mReceiverData.size(); ++receiverIdx)
636 {
637 const ReceiverData& receiver = mReceiverData[receiverIdx];
640
641 // Connected path ID
643 pathId.AddVertex(receiver.Id);
644
645 // We need to check including receiver Id
646 // to be able to match duplicates agains backward subpaths
647 if (not uniqueCheckSet.Insert(pathId.Id))
648 {
649 continue; // Cull duplicate subpaths
650 }
651
652 // See if PathCach already contains this subpath
653 if (not pathCache->Contains(pathId))
654 {
655 outNewSubpaths.emplace_back(
656 NewSubpathEntry{
657 .Subpath = subpath,
658 .Direction = ETraceDirection::Forward,
659 .ReceiverIdx = receiverIdx,
660 .PathId = pathId
661 });
662 }
663 }
664 }
665 }
666 }
667
668 template<class SceneType, class SpecularPathCacheContainer>
669 inline void SpecularRayTracing::ProcessRoutine<SceneType, SpecularPathCacheContainer>::CreateBackwardSubpathsEntries(std::span<TraceResults<Intersection>> traces,
671 std::pmr::vector<NewSubpathEntry>& outNewSubpaths) const
672 {
673 JPL::SpecularPathId pathPartialId{ .Id = mSourceData.Id };
674
675 // We have to rehash subpath that is backwards, accumulating from the head
676 auto hashSequence = [](std::span<const PathNodeType> subpath)
677 {
678 Hash hash;
679 for (auto const& node : subpath | std::views::reverse)
680 {
681 hash.Combine(node.Hash);
682 }
683 return hash.GetCurrent();
684 };
685
686 for (uint32 receiverIdx = 0; receiverIdx < mReceiverData.size(); ++receiverIdx)
687 {
688 const TraceResults<Intersection>& traceResults = traces[receiverIdx];
689 const ReceiverData& receiver = mReceiverData[receiverIdx];
692
693 for (uint32 pathIdx = 0; pathIdx < traceResults.Paths.size(); ++pathIdx)
694 {
695 const auto& path = traceResults.Paths[pathIdx].Nodes;
696
697 // Construct backward path in reverse order (from receiver to soruce)
698 // This will ensure consistent hash/id order with forward paths
699 for (int32 nb = 1; nb <= path.size(); ++nb)
700 {
701 std::span<const PathNodeType> subpath(path.data(), nb);
702
703 // Connected path ID
705 pathId.AddVertex(hashSequence(subpath));
706 pathId.AddVertex(receiver.Id);
707
708 if (not uniqueCheckSet.Insert(pathId.Id))
709 {
710 continue; // Cull duplicate subpaths
711 }
712
713 // See if PathCach already contains this subpath
714 if (not pathCache->Contains(pathId))
715 {
716 outNewSubpaths.emplace_back(
717 NewSubpathEntry{
718 .Subpath = subpath,
719 .Direction = ETraceDirection::Backward,
720 .ReceiverIdx = receiverIdx,
721 .PathId = pathId
722 });
723 }
724 }
725 }
726 }
727 }
728
729 //==========================================================================
730 template<class SceneType, class SpecularPathCacheContainer>
731 template<ETraceDirection PathDirection>
732 inline void SpecularRayTracing::ProcessRoutine<SceneType, SpecularPathCacheContainer>::ConstructImageSources(const Vec3& sourcePosition,
733 std::span<const TraceNode<Intersection>> path,
734 std::span<Vec3> outImageSources) const
735 {
737
739
741 {
742 for (int32 i = 0; i < path.size(); ++i)
743 {
745 outImageSources[i + 1] = Math::GetImageSource(outImageSources[i],
746 mSceneInterface.GetNormal(node.Hit),
747 mSceneInterface.GetPosition(node.Hit));
748 }
749 }
750 else // PathDirection == ETraceDirection::Backward
751 {
752 for (int32 i = path.size() - 1, ISIndex = 0; i >= 0; --i, ++ISIndex)
753 {
755 outImageSources[ISIndex + 1] = Math::GetImageSource(outImageSources[ISIndex],
756 mSceneInterface.GetNormal(node.Hit),
757 mSceneInterface.GetPosition(node.Hit));
758 }
759 }
760 }
761
762 template<class SceneType, class SpecularPathCacheContainer>
763 template<ETraceDirection TraceDirection>
764 inline void SpecularRayTracing::ProcessRoutine<SceneType, SpecularPathCacheContainer>::ConstructImageSources(const Vec3& sourcePosition,
765 std::span<const TraceNode<Intersection>> path,
767 std::span<Vec3> outImageSources) const
768 {
770 {
771 static constexpr auto cISTraceDirection = TraceDirection;
773 }
774 else
775 {
778 }
779 }
780
781 template<class SceneType, class SpecularPathCacheContainer>
782 template<ETraceDirection PathDirection>
783 inline bool SpecularRayTracing::ProcessRoutine<SceneType, SpecularPathCacheContainer>::ValidatePathForListener(std::span<const TraceNode<Intersection>> nodes,
784 std::span<const Vec3> imageSources,
785 const Vec3& listenerPosition) const
786 {
787 // Check that we do intersect the correct surface sequance,
788 // and nothing is obstructing visibility of the image source
789
790 Vec3 R = listenerPosition;
791
793 {
794 for (uint32 i = imageSources.size() - 1, ni = 0; i >= 1; --i, ++ni)
795 {
796 Intersection hit;
797
798 if (not mSceneInterface.Intersect(R, imageSources[i], hit) ||
799 not mSceneInterface.IsSameSurface(hit, nodes[ni].Hit))
800 return false;
801
802 // Small offset to avoid self intersection
803 static constexpr float offset = 0.001f;
804
805 R = mSceneInterface.GetPosition(hit) + mSceneInterface.GetNormal(hit) * offset;
806 }
807 }
808 else
809 {
810 for (auto i = imageSources.size() - 1; i >= 1; --i)
811 {
812 Intersection hit;
813
814 if (not mSceneInterface.Intersect(R, imageSources[i], hit) ||
815 not mSceneInterface.IsSameSurface(hit, nodes[i - 1].Hit))
816 return false;
817
818 // Small offset to avoid self intersection
819 static constexpr float offset = 0.001f;
820
821 R = mSceneInterface.GetPosition(hit) + mSceneInterface.GetNormal(hit) * offset;
822 }
823 }
824
825 // Lastly check visibility between
826 // source and first refleciton point
827 return not mSceneInterface.IsOccluded(R, imageSources[0]);
828 }
829
830 template<class SceneType, class SpecularPathCacheContainer>
831 template<ETraceDirection TraceDirection>
832 inline bool SpecularRayTracing::ProcessRoutine<SceneType, SpecularPathCacheContainer>::ValidatePathForListener(std::span<const TraceNode<Intersection>> nodes,
834 std::span<const Vec3> imageSources,
835 const Vec3& listenerPosition) const
836 {
838 {
839 static constexpr auto cISDirection = TraceDirection;
841 }
842 else
843 {
846 }
847 }
848
849 template<class SceneType, class SpecularPathCacheContainer>
850 inline void SpecularRayTracing::ProcessRoutine<SceneType, SpecularPathCacheContainer>::ValidateNewSubpaths(std::span<NewSubpathEntry> newSubpaths,
851 const Vec3 listenerPosition) const
852 {
856
857
858 // Construct Image Source table
859 auto imageSourceBuffer = ImageSourceBuffer<Vec3>::MakeFor(newSubpaths); //? allocating
860
861 auto validateSubpath = [&](int32 ei)
862 {
863 NewSubpathEntry& entry = newSubpaths[ei];
864 const ReceiverData& receiver = mReceiverData[entry.ReceiverIdx];
865 std::span<Vec3> imageSources = imageSourceBuffer.GetImageSourcesFor(entry.Subpath, ei);
866
868 entry.Subpath,
869 entry.Direction,
871
872 entry.LastImageSource = imageSources.back();
873
874 // This is touching physics scene (potentially needs a lock)
876 entry.Direction,
879
880 if (entry.bIsValid)
881 {
882 AccumulateMaterialAbsorption(entry.Subpath, entry.EnergyLoss);
883 }
884 };
885
886 // Give the caller a chance to prepare for a potentially concurrent set of traces
887 // (e.g. lock physics scene)
888 if constexpr (JPL_HAS_FUNCTION(SceneType, PreTrace()))
889 {
890 mSceneInterface.PreTrace();
891 }
892
894 {
895 mSceneInterface.ParallelFor(static_cast<uint32>(newSubpaths.size()), validateSubpath);
896 }
897 else
898 {
899 for (uint32 ei = 0; ei < newSubpaths.size(); ++ei)
900 {
902 }
903 }
904
905 if constexpr (JPL_HAS_FUNCTION(SceneType, PostTrace()))
906 {
907 mSceneInterface.PostTrace();
908 }
909 }
910
911 template<class SceneType, class SpecularPathCacheContainer>
912 inline void SpecularRayTracing::ProcessRoutine<SceneType, SpecularPathCacheContainer>::AccumulateMaterialAbsorption(std::span<const TraceNode<Intersection>> surfaces,
914 {
915 for (const auto& surfaceHit : surfaces)
916 {
918 if (mSceneInterface.GetMaterialAbsorption(surfaceHit, materialAbsorption))
919 {
921 }
922 }
923 }
924
925 //==========================================================================
926 template<class SceneType, class SpecularPathCacheContainer>
927 inline void SpecularRayTracing::ProcessRoutine<SceneType, SpecularPathCacheContainer>::CacheValidatedSubpaths(std::span<NewSubpathEntry> validatedSubpaths,
929 {
931
932 std::pmr::vector<int32> nodeCache(JPL::GetDefaultMemoryResource());
933 nodeCache.reserve(maxPathOrderHint + 2);
934
935 // Assign source as the first vertex
936 nodeCache.push_back(mSourceData.Id);
937
938 std::pmr::vector<PathNodeType> subpathCopy(JPL::GetDefaultMemoryResource());
940
942
943 // Add new subpaths to Path Caches
944 for (const NewSubpathEntry& entry : validatedSubpaths)
945 {
946 // Note: this is where we need simple integer IDs for the path nodes
947 // that we resolve using Geometry Cache
948
949 // Get a contiguous surface path range
950 subpathCopy.resize(entry.Subpath.size());
951 std::ranges::copy(entry.Subpath, subpathCopy.begin());
952
953 // Backward traced paths has to be reversed
954 if (entry.Direction == ETraceDirection::Backward)
955 {
956 std::ranges::reverse(subpathCopy);
957 }
958
959 // Remove all but the first source node
960 nodeCache.resize(1 + subpathCopy.size());
961
962 // ...essentially asking the caller to convert trace path to a set of surface/node identifiers
963 mSceneInterface.CacheSubpath(subpathCopy, std::span(&nodeCache[1], subpathCopy.size()));
964
965 // Assign receiver as the last vertex
966 nodeCache.push_back(static_cast<int32>(mReceiverData[entry.ReceiverIdx].Id));
967
968 // Add set of Geometry Cache handles to Path Cache
969 JPL::SpecularPathCache<Vec3>* pathCache = mCaches[entry.ReceiverIdx];
971
972 // Add new entry to the receiver's path cache
973 // (this cannot be called concurrently)
974 pathCache->Add(entry.PathId, nodeCache, entry.LastImageSource, entry.EnergyLoss, entry.bIsValid);
975
976 numValidPathsFound += entry.bIsValid;
977 }
978
981 }
982} // namespace JPL
983
#define JPL_ASSERT(inExpression,...)
Main assert macro, usage: JPL_ASSERT(condition, message) or JPL_ASSERT(condition)
Definition ErrorReporting.h:76
#define JPL_PROFILE_SET_INT(Stat, Value)
Definition SpecularRayTracing.h:46
#define JPL_PROFILE(...)
Definition SpecularRayTracing.h:42
#define JPL_HAS_FUNCTION(Type, MemberFunctionCallPrototype)
Definition SpecularRayTracing.h:55
#define JPL_PROFILE_SET_FLOAT(Stat, Value)
Definition SpecularRayTracing.h:50
static JPL_INLINE std::size_t GetRequiredMemorySize(uint32 expectedCount)
Definition ScratchHashSet.h:105
A very experimental specular path cache.
Definition SpecularPathCache.h:49
Definition SpecularRayTracing.h:140
Definition AcousticMaterial.h:36
ScratchHashSet32< uint32, std::identity > ScratchHashSetIdentity
Definition ScratchHashSet.h:38
std::int32_t int32
Definition Core.h:316
JPL_INLINE constexpr void HashCombine32(uint32_t &seed, uint32_t id32)
Definition Hash.h:42
ETraceDirection
Definition SpecularRayTracing.h:68
JPL_INLINE simd reverse(const simd &vec) noexcept
Reverse the order of the lanes.
Definition SIMD.h:2005
std::uint32_t uint32
Definition Core.h:311
std::pmr::memory_resource * GetDefaultMemoryResource() noexcept
Definition Memory.h:42
simd EnergyBands
Definition FrequencyBands.h:36
JPL_INLINE simd min(const simd &a, const simd &b) noexcept
Element-wise min.
Definition SIMD.h:1813
Definition ChannelMap.h:268
A very experimental specular path definition.
Definition SpecularPath.h:40
Node of a traced paths.
Definition SpecularRayTracing.h:84
uint32 Hash
Definition SpecularRayTracing.h:89
IntersectionType Hit
Definition SpecularRayTracing.h:86
Generic parameters used in different kinds of traces.
Definition SpecularRayTracing.h:75
uint32 MaxTraceOrder
Definition SpecularRayTracing.h:77
float MaxRayLength
Definition SpecularRayTracing.h:78
uint32 NumPrimaryRays
Definition SpecularRayTracing.h:76
Result of tracing paths.
Definition SpecularRayTracing.h:102
std::pmr::vector< TracedPath< IntersectionType > > Paths
Definition SpecularRayTracing.h:103
Sequence of traced path intersections.
Definition SpecularRayTracing.h:95
std::pmr::vector< TraceNode< IntersectionType > > Nodes
Definition SpecularRayTracing.h:96
Minimal 4-wide 32-bit float vector implementation for SIMD.
Definition SIMD.h:60
static constexpr std::size_t size() noexcept
Get number of element of the vector.
Definition SIMD.h:97