123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452 |
- /*******************************************************************************
- The content of this file includes portions of the proprietary AUDIOKINETIC Wwise
- Technology released in source code form as part of the game integration package.
- The content of this file may not be used without valid licenses to the
- AUDIOKINETIC Wwise Technology.
- Note that the use of the game engine is subject to the Unreal(R) Engine End User
- License Agreement at https://www.unrealengine.com/en-US/eula/unreal
-
- License Usage
-
- Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use
- this file in accordance with the end user license agreement provided with the
- software or, alternatively, in accordance with the terms contained
- in a written agreement between you and Audiokinetic Inc.
- Copyright (c) 2023 Audiokinetic Inc.
- *******************************************************************************/
- /*=============================================================================
- AkObstructionAndOcclusionService.cpp:
- =============================================================================*/
- #include "ObstructionAndOcclusionService/AkObstructionAndOcclusionService.h"
- #include "AkAudioDevice.h"
- #include "AkComponent.h"
- #include "AkSpatialAudioHelper.h"
- #include "AkAcousticPortal.h"
- #include "Engine/World.h"
- #include "Engine/Engine.h"
- #include "Components/PrimitiveComponent.h"
- #include "Async/Async.h"
- #include "GameFramework/PlayerController.h"
- #include "GameFramework/Pawn.h"
- #define AK_DEBUG_OCCLUSION_PRINT 0
- #if AK_DEBUG_OCCLUSION_PRINT
- static int framecounter = 0;
- #endif
- #define AK_DEBUG_OCCLUSION 0
- #if AK_DEBUG_OCCLUSION
- #include "DrawDebugHelpers.h"
- #endif
- FAkListenerObstructionAndOcclusion::FAkListenerObstructionAndOcclusion(float in_TargetValue, float in_CurrentValue)
- : CurrentValue(in_CurrentValue)
- , TargetValue(in_TargetValue)
- , Rate(0.0f)
- {}
- void FAkListenerObstructionAndOcclusion::SetTarget(float in_TargetValue)
- {
- TargetValue = FMath::Clamp(in_TargetValue, 0.0f, 1.0f);
- const float UAkComponent_OCCLUSION_FADE_RATE = 2.0f; // from 0.0 to 1.0 in 0.5 seconds
- Rate = FMath::Sign(TargetValue - CurrentValue) * UAkComponent_OCCLUSION_FADE_RATE;
- }
- bool FAkListenerObstructionAndOcclusion::Update(float DeltaTime)
- {
- auto OldValue = CurrentValue;
- if (OldValue != TargetValue)
- {
- const auto NewValue = OldValue + Rate * DeltaTime;
- if (OldValue > TargetValue)
- CurrentValue = FMath::Clamp(NewValue, TargetValue, OldValue);
- else
- CurrentValue = FMath::Clamp(NewValue, OldValue, TargetValue);
- AKASSERT(CurrentValue >= 0.f && CurrentValue <= 1.f);
- return true;
- }
- return false;
- }
- bool FAkListenerObstructionAndOcclusion::ReachedTarget()
- {
- return CurrentValue == TargetValue;
- }
- //=====================================================================================
- // FAkListenerObstructionAndOcclusionPair
- //=====================================================================================
- FAkListenerObstructionAndOcclusionPair::FAkListenerObstructionAndOcclusionPair()
- {
- SourceRayCollisions.AddZeroed(NUM_BOUNDING_BOX_TRACE_POINTS);
- ListenerRayCollisions.AddZeroed(NUM_BOUNDING_BOX_TRACE_POINTS);
- SourceTraceHandles.AddDefaulted(NUM_BOUNDING_BOX_TRACE_POINTS);
- ListenerTraceHandles.AddDefaulted(NUM_BOUNDING_BOX_TRACE_POINTS);
- }
- bool FAkListenerObstructionAndOcclusionPair::Update(float DeltaTime)
- {
- if (CurrentCollisionCount != GetCollisionCount())
- {
- CurrentCollisionCount = GetCollisionCount();
- const float ratio = (float)CurrentCollisionCount / NUM_BOUNDING_BOX_TRACE_POINTS;
- Occ.SetTarget(ratio);
- Obs.SetTarget(ratio);
- }
- const bool bObsChanged = Obs.Update(DeltaTime);
- const bool bOccChanged = Occ.Update(DeltaTime);
- return bObsChanged || bOccChanged;
- }
- void FAkListenerObstructionAndOcclusionPair::Reset()
- {
- for (int i = 0; i < NUM_BOUNDING_BOX_TRACE_POINTS; ++i)
- {
- SourceRayCollisions[i] = ListenerRayCollisions[i] = false;
- }
- }
- bool FAkListenerObstructionAndOcclusionPair::ReachedTarget()
- {
- return Obs.ReachedTarget() && Occ.ReachedTarget();
- }
- void FAkListenerObstructionAndOcclusionPair::AsyncTraceFromSource(const FVector& SourcePosition, const FVector& EndPosition, int BoundingBoxPointIndex, ECollisionChannel CollisionChannel, UWorld* World, const FCollisionQueryParams& CollisionParams)
- {
- ensure(BoundingBoxPointIndex < NUM_BOUNDING_BOX_TRACE_POINTS);
- // Check that we're not stacking another async trace on top of one that hasn't completed yet.
- if (!World->IsTraceHandleValid(SourceTraceHandles[BoundingBoxPointIndex], false))
- {
- SourceTraceHandles[BoundingBoxPointIndex] = World->AsyncLineTraceByChannel(EAsyncTraceType::Single, SourcePosition, EndPosition, CollisionChannel, CollisionParams);
- }
- }
- void FAkListenerObstructionAndOcclusionPair::AsyncTraceFromListener(const FVector& ListenerPosition, const FVector& EndPosition, int BoundingBoxPointIndex, ECollisionChannel CollisionChannel, UWorld* World, const FCollisionQueryParams& CollisionParams)
- {
- ensure(BoundingBoxPointIndex < NUM_BOUNDING_BOX_TRACE_POINTS);
- // Check that we're not stacking another async trace on top of one that hasn't completed yet.
- if (!World->IsTraceHandleValid(ListenerTraceHandles[BoundingBoxPointIndex], false))
- {
- ListenerTraceHandles[BoundingBoxPointIndex] = World->AsyncLineTraceByChannel(EAsyncTraceType::Single, ListenerPosition, EndPosition, CollisionChannel, CollisionParams);
- }
- }
- int FAkListenerObstructionAndOcclusionPair::GetCollisionCount()
- {
- int CollisionCount = 0;
- for (int i = 0; i < NUM_BOUNDING_BOX_TRACE_POINTS; ++i)
- {
- CollisionCount += (SourceRayCollisions[i] || ListenerRayCollisions[i]) ? 1 : 0;
- }
- return CollisionCount;
- }
- void FAkListenerObstructionAndOcclusionPair::CheckTraceResults(UWorld* World)
- {
- CheckListenerTraceHandles(World);
- CheckSourceTraceHandles(World);
- }
- void FAkListenerObstructionAndOcclusionPair::CheckListenerTraceHandles(UWorld* World)
- {
- for (int BoundingBoxPointIndex = 0; BoundingBoxPointIndex < NUM_BOUNDING_BOX_TRACE_POINTS; ++BoundingBoxPointIndex)
- {
- if (ListenerTraceHandles[BoundingBoxPointIndex]._Data.FrameNumber != 0)
- {
- FTraceDatum OutData;
- if (World->QueryTraceData(ListenerTraceHandles[BoundingBoxPointIndex], OutData))
- {
- ListenerTraceHandles[BoundingBoxPointIndex]._Data.FrameNumber = 0;
- ListenerRayCollisions[BoundingBoxPointIndex] = OutData.OutHits.Num() > 0;
- }
- }
- }
- }
- void FAkListenerObstructionAndOcclusionPair::CheckSourceTraceHandles(UWorld* World)
- {
- for (int BoundingBoxPointIndex = 0; BoundingBoxPointIndex < NUM_BOUNDING_BOX_TRACE_POINTS; ++BoundingBoxPointIndex)
- {
- if (SourceTraceHandles[BoundingBoxPointIndex]._Data.FrameNumber != 0)
- {
- FTraceDatum OutData;
- if (World->QueryTraceData(SourceTraceHandles[BoundingBoxPointIndex], OutData))
- {
- SourceTraceHandles[BoundingBoxPointIndex]._Data.FrameNumber = 0;
- SourceRayCollisions[BoundingBoxPointIndex] = OutData.OutHits.Num() > 0;
- }
- }
- }
- }
- //=====================================================================================
- // AkObstructionAndOcclusionService
- //=====================================================================================
- void AkObstructionAndOcclusionService::_Init(UWorld* in_world, float in_refreshInterval)
- {
- if (in_refreshInterval > 0 && in_world != nullptr)
- LastObstructionAndOcclusionRefresh = in_world->GetTimeSeconds() + FMath::RandRange(0.0f, in_refreshInterval);
- else
- LastObstructionAndOcclusionRefresh = -1;
- }
- void AkObstructionAndOcclusionService::RefreshObstructionAndOcclusion(const UAkComponentSet& in_Listeners, const FVector& SourcePosition, const AActor* Actor, AkRoomID RoomID, ECollisionChannel in_collisionChannel, const float DeltaTime, float OcclusionRefreshInterval)
- {
- auto AudioDevice = FAkAudioDevice::Get();
- // Fade the active occlusions
- bool StillClearingObsOcc = false;
- for (auto It = ListenerInfoMap.CreateIterator(); It; ++It)
- {
- AkGameObjectID Listener = It->Key;
- if (in_Listeners.Find((UAkComponent*)Listener) == nullptr)
- {
- It.RemoveCurrent();
- continue;
- }
- FAkListenerObstructionAndOcclusionPair& ObsOccPair = It->Value;
- ObsOccPair.CheckTraceResults(Actor->GetWorld());
- if (ObsOccPair.Update(DeltaTime) && AudioDevice)
- {
- SetObstructionAndOcclusion(Listener, ObsOccPair.Obs.CurrentValue);
- }
- if (ClearingObstructionAndOcclusion)
- {
- StillClearingObsOcc |= !ObsOccPair.ReachedTarget();
- }
- }
- if (ClearingObstructionAndOcclusion)
- {
- ClearingObstructionAndOcclusion = StillClearingObsOcc;
- return;
- }
- // Compute occlusion only when needed.
- // Have to have "LastObstructionAndOcclusionRefresh == -1" because GetWorld() might return nullptr in UAkComponent's constructor,
- // preventing us from initializing it to something smart.
- const UWorld* CurrentWorld = Actor ? Actor->GetWorld() : nullptr;
- if (CurrentWorld)
- {
- float CurrentTime = CurrentWorld->GetTimeSeconds();
- if (CurrentTime < LastObstructionAndOcclusionRefresh && LastObstructionAndOcclusionRefresh - CurrentTime > OcclusionRefreshInterval)
- {
- // Occlusion refresh interval was made shorter since the last refresh, we need to re-distribute the next random calculation
- LastObstructionAndOcclusionRefresh = CurrentTime + FMath::RandRange(0.0f, OcclusionRefreshInterval);
- }
- if (LastObstructionAndOcclusionRefresh == -1 || (CurrentTime - LastObstructionAndOcclusionRefresh) >= OcclusionRefreshInterval)
- {
- LastObstructionAndOcclusionRefresh = CurrentTime;
- for (auto& Listener : in_Listeners)
- {
- auto& MapEntry = ListenerInfoMap.FindOrAdd(Listener->GetAkGameObjectID());
- MapEntry.Position = Listener->GetPosition();
- }
- CalculateObstructionAndOcclusionValues(in_Listeners, SourcePosition, Actor, RoomID, in_collisionChannel);
- }
- }
- }
- void AkObstructionAndOcclusionService::CalculateObstructionAndOcclusionValues(const UAkComponentSet& in_Listeners, const FVector& SourcePosition, const AActor* Actor, AkRoomID RoomID, ECollisionChannel in_collisionChannel, bool bAsync /* = true */)
- {
- auto CurrentWorld = Actor->GetWorld();
- if (!CurrentWorld)
- return;
- static const FName NAME_SoundOcclusion = TEXT("SoundOcclusion");
- FCollisionQueryParams CollisionParams(NAME_SoundOcclusion, true, Actor);
- auto PlayerController = GEngine->GetFirstLocalPlayerController(CurrentWorld);
- if (PlayerController)
- CollisionParams.AddIgnoredActor(PlayerController->GetPawn());
- for (auto& Listener : in_Listeners)
- {
- if (RoomID != Listener->GetSpatialAudioRoom())
- continue;
- auto MapEntry = ListenerInfoMap.Find(Listener->GetAkGameObjectID());
- if (MapEntry == nullptr)
- continue;
- const FVector ListenerPosition = MapEntry->Position;
- FHitResult OutHit;
- const bool bNowOccluded = CurrentWorld->LineTraceSingleByChannel(OutHit, SourcePosition, ListenerPosition, in_collisionChannel, CollisionParams);
- if (bNowOccluded)
- {
- FBox BoundingBox;
- AActor* HitActor = AkSpatialAudioHelper::GetActorFromHitResult(OutHit);
- if (HitActor)
- {
- BoundingBox = HitActor->GetComponentsBoundingBox();
- }
- else if (OutHit.Component.IsValid())
- {
- BoundingBox = OutHit.Component->Bounds.GetBox();
- }
- // Translate the impact point to the bounding box of the obstacle
- const FVector Points[] =
- {
- FVector(OutHit.ImpactPoint.X, BoundingBox.Min.Y, BoundingBox.Min.Z),
- FVector(OutHit.ImpactPoint.X, BoundingBox.Min.Y, BoundingBox.Max.Z),
- FVector(OutHit.ImpactPoint.X, BoundingBox.Max.Y, BoundingBox.Min.Z),
- FVector(OutHit.ImpactPoint.X, BoundingBox.Max.Y, BoundingBox.Max.Z),
- FVector(BoundingBox.Min.X, OutHit.ImpactPoint.Y, BoundingBox.Min.Z),
- FVector(BoundingBox.Min.X, OutHit.ImpactPoint.Y, BoundingBox.Max.Z),
- FVector(BoundingBox.Max.X, OutHit.ImpactPoint.Y, BoundingBox.Min.Z),
- FVector(BoundingBox.Max.X, OutHit.ImpactPoint.Y, BoundingBox.Max.Z),
- FVector(BoundingBox.Min.X, BoundingBox.Min.Y, OutHit.ImpactPoint.Z),
- FVector(BoundingBox.Min.X, BoundingBox.Max.Y, OutHit.ImpactPoint.Z),
- FVector(BoundingBox.Max.X, BoundingBox.Min.Y, OutHit.ImpactPoint.Z),
- FVector(BoundingBox.Max.X, BoundingBox.Max.Y, OutHit.ImpactPoint.Z)
- };
- if (bAsync)
- {
- for (int PointIndex = 0; PointIndex < NUM_BOUNDING_BOX_TRACE_POINTS; ++PointIndex)
- {
- auto Point = Points[PointIndex];
- MapEntry->AsyncTraceFromListener(ListenerPosition, Point, PointIndex, in_collisionChannel, CurrentWorld, CollisionParams);
- MapEntry->AsyncTraceFromSource(SourcePosition, Point, PointIndex, in_collisionChannel, CurrentWorld, CollisionParams);
- }
- }
- else
- {
- // Compute the number of "second order paths" that are also obstructed. This will allow us to approximate
- // "how obstructed" the source is.
- int32 NumObstructedPaths = 0;
- for (const auto& Point : Points)
- {
- if (CurrentWorld->LineTraceSingleByChannel(OutHit, ListenerPosition, Point, in_collisionChannel, CollisionParams) ||
- CurrentWorld->LineTraceSingleByChannel(OutHit, SourcePosition, Point, in_collisionChannel, CollisionParams))
- ++NumObstructedPaths;
- }
- // Modulate occlusion by blocked secondary paths.
- const float ratio = (float)NumObstructedPaths / NUM_BOUNDING_BOX_TRACE_POINTS;
- MapEntry->Occ.SetTarget(ratio);
- MapEntry->Obs.SetTarget(ratio);
- }
- #if AK_DEBUG_OCCLUSION
- check(IsInGameThread());
- // Draw bounding box and "second order paths"
- //UE_LOG(LogAkAudio, Log, TEXT("Target Occlusion level: %f"), ListenerOcclusionInfo[ListenerIdx].TargetValue);
- FlushPersistentDebugLines(CurrentWorld);
- FlushDebugStrings(CurrentWorld);
- DrawDebugBox(CurrentWorld, BoundingBox.GetCenter(), BoundingBox.GetExtent(), FColor::White, false, 4);
- DrawDebugPoint(CurrentWorld, ListenerPosition, 10.0f, FColor(0, 255, 0), false, 4);
- DrawDebugPoint(CurrentWorld, SourcePosition, 10.0f, FColor(0, 255, 0), false, 4);
- DrawDebugPoint(CurrentWorld, OutHit.ImpactPoint, 10.0f, FColor(0, 255, 0), false, 4);
- for (int32 i = 0; i < NUM_BOUNDING_BOX_TRACE_POINTS; i++)
- {
- DrawDebugPoint(CurrentWorld, Points[i], 10.0f, FColor(255, 255, 0), false, 4);
- DrawDebugString(CurrentWorld, Points[i], FString::Printf(TEXT("%d"), i), nullptr, FColor::White, 4);
- DrawDebugLine(CurrentWorld, Points[i], ListenerPosition, FColor::Cyan, false, 4);
- DrawDebugLine(CurrentWorld, Points[i], SourcePosition, FColor::Cyan, false, 4);
- }
- FColor LineColor = FColor::MakeRedToGreenColorFromScalar(1.0f - MapEntry->Occ.TargetValue);
- DrawDebugLine(CurrentWorld, ListenerPosition, SourcePosition, LineColor, false, 4);
- #endif // AK_DEBUG_OCCLUSION
- }
- else
- {
- MapEntry->Occ.SetTarget(0.0f);
- MapEntry->Obs.SetTarget(0.0f);
- MapEntry->Reset();
- }
- }
- }
- void AkObstructionAndOcclusionService::SetObstructionAndOcclusion(const UAkComponentSet& in_Listeners, AkRoomID RoomID)
- {
- FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get();
- if (!AkAudioDevice)
- return;
- for (auto& Listener : in_Listeners)
- {
- if (RoomID != Listener->GetSpatialAudioRoom())
- continue;
- auto MapEntry = ListenerInfoMap.Find(Listener->GetAkGameObjectID());
- if (MapEntry == nullptr)
- continue;
- MapEntry->Occ.CurrentValue = MapEntry->Occ.TargetValue;
- SetObstructionAndOcclusion(Listener->GetAkGameObjectID(), MapEntry->Obs.CurrentValue/*, Occlusion.CurrentValue*/);
- }
- }
- void AkObstructionAndOcclusionService::ClearOcclusionValues()
- {
- ClearingObstructionAndOcclusion = false;
- for (auto& ListenerPack : ListenerInfoMap)
- {
- FAkListenerObstructionAndOcclusionPair& Pair = ListenerPack.Value;
- Pair.Occ.SetTarget(0.0f);
- Pair.Obs.SetTarget(0.0f);
- ClearingObstructionAndOcclusion |= !Pair.ReachedTarget();
- }
- }
- void AkObstructionAndOcclusionService::Tick(const UAkComponentSet& in_Listeners, const FVector& SourcePosition, const AActor* Actor, AkRoomID RoomID, ECollisionChannel in_collisionChannel, float DeltaTime, float OcclusionRefreshInterval)
- {
- // Check Occlusion/Obstruction, if enabled
- if (OcclusionRefreshInterval > 0.0f || ClearingObstructionAndOcclusion)
- {
- RefreshObstructionAndOcclusion(in_Listeners, SourcePosition, Actor, RoomID, in_collisionChannel, DeltaTime, OcclusionRefreshInterval);
- }
- else if (OcclusionRefreshInterval != PreviousRefreshInterval)
- {
- // Reset the occlusion obstruction pairs so that the occlusion is correctly recalculated.
- for (auto& ListenerPack : ListenerInfoMap)
- {
- FAkListenerObstructionAndOcclusionPair& Pair = ListenerPack.Value;
- Pair.Reset();
- }
- if (OcclusionRefreshInterval <= 0.0f)
- ClearOcclusionValues();
- }
- PreviousRefreshInterval = OcclusionRefreshInterval;
- }
- void AkObstructionAndOcclusionService::UpdateObstructionAndOcclusion(const UAkComponentSet& in_Listeners, const FVector& SourcePosition, const AActor* Actor, AkRoomID RoomID, ECollisionChannel in_collisionChannel, float OcclusionRefreshInterval)
- {
- if ((OcclusionRefreshInterval > 0.f || ClearingObstructionAndOcclusion) && Actor)
- {
- for (auto& Listener : in_Listeners)
- {
- auto& MapEntry = ListenerInfoMap.FindOrAdd(Listener->GetAkGameObjectID());
- MapEntry.Position = Listener->GetPosition();
- }
- CalculateObstructionAndOcclusionValues(in_Listeners, SourcePosition, Actor, RoomID, in_collisionChannel, false);
- for (auto& ListenerPair : ListenerInfoMap)
- {
- ListenerPair.Value.Obs.CurrentValue = ListenerPair.Value.Obs.TargetValue;
- ListenerPair.Value.Occ.CurrentValue = ListenerPair.Value.Occ.TargetValue;
- }
- SetObstructionAndOcclusion(in_Listeners, RoomID);
- }
- }
|