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