AkObstructionAndOcclusionService.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. /*******************************************************************************
  2. The content of this file includes portions of the proprietary AUDIOKINETIC Wwise
  3. Technology released in source code form as part of the game integration package.
  4. The content of this file may not be used without valid licenses to the
  5. AUDIOKINETIC Wwise Technology.
  6. Note that the use of the game engine is subject to the Unreal(R) Engine End User
  7. License Agreement at https://www.unrealengine.com/en-US/eula/unreal
  8. License Usage
  9. Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use
  10. this file in accordance with the end user license agreement provided with the
  11. software or, alternatively, in accordance with the terms contained
  12. in a written agreement between you and Audiokinetic Inc.
  13. Copyright (c) 2023 Audiokinetic Inc.
  14. *******************************************************************************/
  15. /*=============================================================================
  16. AkObstructionAndOcclusionService.cpp:
  17. =============================================================================*/
  18. #include "ObstructionAndOcclusionService/AkObstructionAndOcclusionService.h"
  19. #include "AkAudioDevice.h"
  20. #include "AkComponent.h"
  21. #include "AkSpatialAudioHelper.h"
  22. #include "AkAcousticPortal.h"
  23. #include "Engine/World.h"
  24. #include "Engine/Engine.h"
  25. #include "Components/PrimitiveComponent.h"
  26. #include "Async/Async.h"
  27. #include "GameFramework/PlayerController.h"
  28. #include "GameFramework/Pawn.h"
  29. #define AK_DEBUG_OCCLUSION_PRINT 0
  30. #if AK_DEBUG_OCCLUSION_PRINT
  31. static int framecounter = 0;
  32. #endif
  33. #define AK_DEBUG_OCCLUSION 0
  34. #if AK_DEBUG_OCCLUSION
  35. #include "DrawDebugHelpers.h"
  36. #endif
  37. FAkListenerObstructionAndOcclusion::FAkListenerObstructionAndOcclusion(float in_TargetValue, float in_CurrentValue)
  38. : CurrentValue(in_CurrentValue)
  39. , TargetValue(in_TargetValue)
  40. , Rate(0.0f)
  41. {}
  42. void FAkListenerObstructionAndOcclusion::SetTarget(float in_TargetValue)
  43. {
  44. TargetValue = FMath::Clamp(in_TargetValue, 0.0f, 1.0f);
  45. const float UAkComponent_OCCLUSION_FADE_RATE = 2.0f; // from 0.0 to 1.0 in 0.5 seconds
  46. Rate = FMath::Sign(TargetValue - CurrentValue) * UAkComponent_OCCLUSION_FADE_RATE;
  47. }
  48. bool FAkListenerObstructionAndOcclusion::Update(float DeltaTime)
  49. {
  50. auto OldValue = CurrentValue;
  51. if (OldValue != TargetValue)
  52. {
  53. const auto NewValue = OldValue + Rate * DeltaTime;
  54. if (OldValue > TargetValue)
  55. CurrentValue = FMath::Clamp(NewValue, TargetValue, OldValue);
  56. else
  57. CurrentValue = FMath::Clamp(NewValue, OldValue, TargetValue);
  58. AKASSERT(CurrentValue >= 0.f && CurrentValue <= 1.f);
  59. return true;
  60. }
  61. return false;
  62. }
  63. bool FAkListenerObstructionAndOcclusion::ReachedTarget()
  64. {
  65. return CurrentValue == TargetValue;
  66. }
  67. //=====================================================================================
  68. // FAkListenerObstructionAndOcclusionPair
  69. //=====================================================================================
  70. FAkListenerObstructionAndOcclusionPair::FAkListenerObstructionAndOcclusionPair()
  71. {
  72. SourceRayCollisions.AddZeroed(NUM_BOUNDING_BOX_TRACE_POINTS);
  73. ListenerRayCollisions.AddZeroed(NUM_BOUNDING_BOX_TRACE_POINTS);
  74. SourceTraceHandles.AddDefaulted(NUM_BOUNDING_BOX_TRACE_POINTS);
  75. ListenerTraceHandles.AddDefaulted(NUM_BOUNDING_BOX_TRACE_POINTS);
  76. }
  77. bool FAkListenerObstructionAndOcclusionPair::Update(float DeltaTime)
  78. {
  79. if (CurrentCollisionCount != GetCollisionCount())
  80. {
  81. CurrentCollisionCount = GetCollisionCount();
  82. const float ratio = (float)CurrentCollisionCount / NUM_BOUNDING_BOX_TRACE_POINTS;
  83. Occ.SetTarget(ratio);
  84. Obs.SetTarget(ratio);
  85. }
  86. const bool bObsChanged = Obs.Update(DeltaTime);
  87. const bool bOccChanged = Occ.Update(DeltaTime);
  88. return bObsChanged || bOccChanged;
  89. }
  90. void FAkListenerObstructionAndOcclusionPair::Reset()
  91. {
  92. for (int i = 0; i < NUM_BOUNDING_BOX_TRACE_POINTS; ++i)
  93. {
  94. SourceRayCollisions[i] = ListenerRayCollisions[i] = false;
  95. }
  96. }
  97. bool FAkListenerObstructionAndOcclusionPair::ReachedTarget()
  98. {
  99. return Obs.ReachedTarget() && Occ.ReachedTarget();
  100. }
  101. void FAkListenerObstructionAndOcclusionPair::AsyncTraceFromSource(const FVector& SourcePosition, const FVector& EndPosition, int BoundingBoxPointIndex, ECollisionChannel CollisionChannel, UWorld* World, const FCollisionQueryParams& CollisionParams)
  102. {
  103. ensure(BoundingBoxPointIndex < NUM_BOUNDING_BOX_TRACE_POINTS);
  104. // Check that we're not stacking another async trace on top of one that hasn't completed yet.
  105. if (!World->IsTraceHandleValid(SourceTraceHandles[BoundingBoxPointIndex], false))
  106. {
  107. SourceTraceHandles[BoundingBoxPointIndex] = World->AsyncLineTraceByChannel(EAsyncTraceType::Single, SourcePosition, EndPosition, CollisionChannel, CollisionParams);
  108. }
  109. }
  110. void FAkListenerObstructionAndOcclusionPair::AsyncTraceFromListener(const FVector& ListenerPosition, const FVector& EndPosition, int BoundingBoxPointIndex, ECollisionChannel CollisionChannel, UWorld* World, const FCollisionQueryParams& CollisionParams)
  111. {
  112. ensure(BoundingBoxPointIndex < NUM_BOUNDING_BOX_TRACE_POINTS);
  113. // Check that we're not stacking another async trace on top of one that hasn't completed yet.
  114. if (!World->IsTraceHandleValid(ListenerTraceHandles[BoundingBoxPointIndex], false))
  115. {
  116. ListenerTraceHandles[BoundingBoxPointIndex] = World->AsyncLineTraceByChannel(EAsyncTraceType::Single, ListenerPosition, EndPosition, CollisionChannel, CollisionParams);
  117. }
  118. }
  119. int FAkListenerObstructionAndOcclusionPair::GetCollisionCount()
  120. {
  121. int CollisionCount = 0;
  122. for (int i = 0; i < NUM_BOUNDING_BOX_TRACE_POINTS; ++i)
  123. {
  124. CollisionCount += (SourceRayCollisions[i] || ListenerRayCollisions[i]) ? 1 : 0;
  125. }
  126. return CollisionCount;
  127. }
  128. void FAkListenerObstructionAndOcclusionPair::CheckTraceResults(UWorld* World)
  129. {
  130. CheckListenerTraceHandles(World);
  131. CheckSourceTraceHandles(World);
  132. }
  133. void FAkListenerObstructionAndOcclusionPair::CheckListenerTraceHandles(UWorld* World)
  134. {
  135. for (int BoundingBoxPointIndex = 0; BoundingBoxPointIndex < NUM_BOUNDING_BOX_TRACE_POINTS; ++BoundingBoxPointIndex)
  136. {
  137. if (ListenerTraceHandles[BoundingBoxPointIndex]._Data.FrameNumber != 0)
  138. {
  139. FTraceDatum OutData;
  140. if (World->QueryTraceData(ListenerTraceHandles[BoundingBoxPointIndex], OutData))
  141. {
  142. ListenerTraceHandles[BoundingBoxPointIndex]._Data.FrameNumber = 0;
  143. ListenerRayCollisions[BoundingBoxPointIndex] = OutData.OutHits.Num() > 0;
  144. }
  145. }
  146. }
  147. }
  148. void FAkListenerObstructionAndOcclusionPair::CheckSourceTraceHandles(UWorld* World)
  149. {
  150. for (int BoundingBoxPointIndex = 0; BoundingBoxPointIndex < NUM_BOUNDING_BOX_TRACE_POINTS; ++BoundingBoxPointIndex)
  151. {
  152. if (SourceTraceHandles[BoundingBoxPointIndex]._Data.FrameNumber != 0)
  153. {
  154. FTraceDatum OutData;
  155. if (World->QueryTraceData(SourceTraceHandles[BoundingBoxPointIndex], OutData))
  156. {
  157. SourceTraceHandles[BoundingBoxPointIndex]._Data.FrameNumber = 0;
  158. SourceRayCollisions[BoundingBoxPointIndex] = OutData.OutHits.Num() > 0;
  159. }
  160. }
  161. }
  162. }
  163. //=====================================================================================
  164. // AkObstructionAndOcclusionService
  165. //=====================================================================================
  166. void AkObstructionAndOcclusionService::_Init(UWorld* in_world, float in_refreshInterval)
  167. {
  168. if (in_refreshInterval > 0 && in_world != nullptr)
  169. LastObstructionAndOcclusionRefresh = in_world->GetTimeSeconds() + FMath::RandRange(0.0f, in_refreshInterval);
  170. else
  171. LastObstructionAndOcclusionRefresh = -1;
  172. }
  173. void AkObstructionAndOcclusionService::RefreshObstructionAndOcclusion(const UAkComponentSet& in_Listeners, const FVector& SourcePosition, const AActor* Actor, AkRoomID RoomID, ECollisionChannel in_collisionChannel, const float DeltaTime, float OcclusionRefreshInterval)
  174. {
  175. auto AudioDevice = FAkAudioDevice::Get();
  176. // Fade the active occlusions
  177. bool StillClearingObsOcc = false;
  178. for (auto It = ListenerInfoMap.CreateIterator(); It; ++It)
  179. {
  180. AkGameObjectID Listener = It->Key;
  181. if (in_Listeners.Find((UAkComponent*)Listener) == nullptr)
  182. {
  183. It.RemoveCurrent();
  184. continue;
  185. }
  186. FAkListenerObstructionAndOcclusionPair& ObsOccPair = It->Value;
  187. ObsOccPair.CheckTraceResults(Actor->GetWorld());
  188. if (ObsOccPair.Update(DeltaTime) && AudioDevice)
  189. {
  190. SetObstructionAndOcclusion(Listener, ObsOccPair.Obs.CurrentValue);
  191. }
  192. if (ClearingObstructionAndOcclusion)
  193. {
  194. StillClearingObsOcc |= !ObsOccPair.ReachedTarget();
  195. }
  196. }
  197. if (ClearingObstructionAndOcclusion)
  198. {
  199. ClearingObstructionAndOcclusion = StillClearingObsOcc;
  200. return;
  201. }
  202. // Compute occlusion only when needed.
  203. // Have to have "LastObstructionAndOcclusionRefresh == -1" because GetWorld() might return nullptr in UAkComponent's constructor,
  204. // preventing us from initializing it to something smart.
  205. const UWorld* CurrentWorld = Actor ? Actor->GetWorld() : nullptr;
  206. if (CurrentWorld)
  207. {
  208. float CurrentTime = CurrentWorld->GetTimeSeconds();
  209. if (CurrentTime < LastObstructionAndOcclusionRefresh && LastObstructionAndOcclusionRefresh - CurrentTime > OcclusionRefreshInterval)
  210. {
  211. // Occlusion refresh interval was made shorter since the last refresh, we need to re-distribute the next random calculation
  212. LastObstructionAndOcclusionRefresh = CurrentTime + FMath::RandRange(0.0f, OcclusionRefreshInterval);
  213. }
  214. if (LastObstructionAndOcclusionRefresh == -1 || (CurrentTime - LastObstructionAndOcclusionRefresh) >= OcclusionRefreshInterval)
  215. {
  216. LastObstructionAndOcclusionRefresh = CurrentTime;
  217. for (auto& Listener : in_Listeners)
  218. {
  219. auto& MapEntry = ListenerInfoMap.FindOrAdd(Listener->GetAkGameObjectID());
  220. MapEntry.Position = Listener->GetPosition();
  221. }
  222. CalculateObstructionAndOcclusionValues(in_Listeners, SourcePosition, Actor, RoomID, in_collisionChannel);
  223. }
  224. }
  225. }
  226. void AkObstructionAndOcclusionService::CalculateObstructionAndOcclusionValues(const UAkComponentSet& in_Listeners, const FVector& SourcePosition, const AActor* Actor, AkRoomID RoomID, ECollisionChannel in_collisionChannel, bool bAsync /* = true */)
  227. {
  228. auto CurrentWorld = Actor->GetWorld();
  229. if (!CurrentWorld)
  230. return;
  231. static const FName NAME_SoundOcclusion = TEXT("SoundOcclusion");
  232. FCollisionQueryParams CollisionParams(NAME_SoundOcclusion, true, Actor);
  233. auto PlayerController = GEngine->GetFirstLocalPlayerController(CurrentWorld);
  234. if (PlayerController)
  235. CollisionParams.AddIgnoredActor(PlayerController->GetPawn());
  236. for (auto& Listener : in_Listeners)
  237. {
  238. if (RoomID != Listener->GetSpatialAudioRoom())
  239. continue;
  240. auto MapEntry = ListenerInfoMap.Find(Listener->GetAkGameObjectID());
  241. if (MapEntry == nullptr)
  242. continue;
  243. const FVector ListenerPosition = MapEntry->Position;
  244. FHitResult OutHit;
  245. const bool bNowOccluded = CurrentWorld->LineTraceSingleByChannel(OutHit, SourcePosition, ListenerPosition, in_collisionChannel, CollisionParams);
  246. if (bNowOccluded)
  247. {
  248. FBox BoundingBox;
  249. AActor* HitActor = AkSpatialAudioHelper::GetActorFromHitResult(OutHit);
  250. if (HitActor)
  251. {
  252. BoundingBox = HitActor->GetComponentsBoundingBox();
  253. }
  254. else if (OutHit.Component.IsValid())
  255. {
  256. BoundingBox = OutHit.Component->Bounds.GetBox();
  257. }
  258. // Translate the impact point to the bounding box of the obstacle
  259. const FVector Points[] =
  260. {
  261. FVector(OutHit.ImpactPoint.X, BoundingBox.Min.Y, BoundingBox.Min.Z),
  262. FVector(OutHit.ImpactPoint.X, BoundingBox.Min.Y, BoundingBox.Max.Z),
  263. FVector(OutHit.ImpactPoint.X, BoundingBox.Max.Y, BoundingBox.Min.Z),
  264. FVector(OutHit.ImpactPoint.X, BoundingBox.Max.Y, BoundingBox.Max.Z),
  265. FVector(BoundingBox.Min.X, OutHit.ImpactPoint.Y, BoundingBox.Min.Z),
  266. FVector(BoundingBox.Min.X, OutHit.ImpactPoint.Y, BoundingBox.Max.Z),
  267. FVector(BoundingBox.Max.X, OutHit.ImpactPoint.Y, BoundingBox.Min.Z),
  268. FVector(BoundingBox.Max.X, OutHit.ImpactPoint.Y, BoundingBox.Max.Z),
  269. FVector(BoundingBox.Min.X, BoundingBox.Min.Y, OutHit.ImpactPoint.Z),
  270. FVector(BoundingBox.Min.X, BoundingBox.Max.Y, OutHit.ImpactPoint.Z),
  271. FVector(BoundingBox.Max.X, BoundingBox.Min.Y, OutHit.ImpactPoint.Z),
  272. FVector(BoundingBox.Max.X, BoundingBox.Max.Y, OutHit.ImpactPoint.Z)
  273. };
  274. if (bAsync)
  275. {
  276. for (int PointIndex = 0; PointIndex < NUM_BOUNDING_BOX_TRACE_POINTS; ++PointIndex)
  277. {
  278. auto Point = Points[PointIndex];
  279. MapEntry->AsyncTraceFromListener(ListenerPosition, Point, PointIndex, in_collisionChannel, CurrentWorld, CollisionParams);
  280. MapEntry->AsyncTraceFromSource(SourcePosition, Point, PointIndex, in_collisionChannel, CurrentWorld, CollisionParams);
  281. }
  282. }
  283. else
  284. {
  285. // Compute the number of "second order paths" that are also obstructed. This will allow us to approximate
  286. // "how obstructed" the source is.
  287. int32 NumObstructedPaths = 0;
  288. for (const auto& Point : Points)
  289. {
  290. if (CurrentWorld->LineTraceSingleByChannel(OutHit, ListenerPosition, Point, in_collisionChannel, CollisionParams) ||
  291. CurrentWorld->LineTraceSingleByChannel(OutHit, SourcePosition, Point, in_collisionChannel, CollisionParams))
  292. ++NumObstructedPaths;
  293. }
  294. // Modulate occlusion by blocked secondary paths.
  295. const float ratio = (float)NumObstructedPaths / NUM_BOUNDING_BOX_TRACE_POINTS;
  296. MapEntry->Occ.SetTarget(ratio);
  297. MapEntry->Obs.SetTarget(ratio);
  298. }
  299. #if AK_DEBUG_OCCLUSION
  300. check(IsInGameThread());
  301. // Draw bounding box and "second order paths"
  302. //UE_LOG(LogAkAudio, Log, TEXT("Target Occlusion level: %f"), ListenerOcclusionInfo[ListenerIdx].TargetValue);
  303. FlushPersistentDebugLines(CurrentWorld);
  304. FlushDebugStrings(CurrentWorld);
  305. DrawDebugBox(CurrentWorld, BoundingBox.GetCenter(), BoundingBox.GetExtent(), FColor::White, false, 4);
  306. DrawDebugPoint(CurrentWorld, ListenerPosition, 10.0f, FColor(0, 255, 0), false, 4);
  307. DrawDebugPoint(CurrentWorld, SourcePosition, 10.0f, FColor(0, 255, 0), false, 4);
  308. DrawDebugPoint(CurrentWorld, OutHit.ImpactPoint, 10.0f, FColor(0, 255, 0), false, 4);
  309. for (int32 i = 0; i < NUM_BOUNDING_BOX_TRACE_POINTS; i++)
  310. {
  311. DrawDebugPoint(CurrentWorld, Points[i], 10.0f, FColor(255, 255, 0), false, 4);
  312. DrawDebugString(CurrentWorld, Points[i], FString::Printf(TEXT("%d"), i), nullptr, FColor::White, 4);
  313. DrawDebugLine(CurrentWorld, Points[i], ListenerPosition, FColor::Cyan, false, 4);
  314. DrawDebugLine(CurrentWorld, Points[i], SourcePosition, FColor::Cyan, false, 4);
  315. }
  316. FColor LineColor = FColor::MakeRedToGreenColorFromScalar(1.0f - MapEntry->Occ.TargetValue);
  317. DrawDebugLine(CurrentWorld, ListenerPosition, SourcePosition, LineColor, false, 4);
  318. #endif // AK_DEBUG_OCCLUSION
  319. }
  320. else
  321. {
  322. MapEntry->Occ.SetTarget(0.0f);
  323. MapEntry->Obs.SetTarget(0.0f);
  324. MapEntry->Reset();
  325. }
  326. }
  327. }
  328. void AkObstructionAndOcclusionService::SetObstructionAndOcclusion(const UAkComponentSet& in_Listeners, AkRoomID RoomID)
  329. {
  330. FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get();
  331. if (!AkAudioDevice)
  332. return;
  333. for (auto& Listener : in_Listeners)
  334. {
  335. if (RoomID != Listener->GetSpatialAudioRoom())
  336. continue;
  337. auto MapEntry = ListenerInfoMap.Find(Listener->GetAkGameObjectID());
  338. if (MapEntry == nullptr)
  339. continue;
  340. MapEntry->Occ.CurrentValue = MapEntry->Occ.TargetValue;
  341. SetObstructionAndOcclusion(Listener->GetAkGameObjectID(), MapEntry->Obs.CurrentValue/*, Occlusion.CurrentValue*/);
  342. }
  343. }
  344. void AkObstructionAndOcclusionService::ClearOcclusionValues()
  345. {
  346. ClearingObstructionAndOcclusion = false;
  347. for (auto& ListenerPack : ListenerInfoMap)
  348. {
  349. FAkListenerObstructionAndOcclusionPair& Pair = ListenerPack.Value;
  350. Pair.Occ.SetTarget(0.0f);
  351. Pair.Obs.SetTarget(0.0f);
  352. ClearingObstructionAndOcclusion |= !Pair.ReachedTarget();
  353. }
  354. }
  355. void AkObstructionAndOcclusionService::Tick(const UAkComponentSet& in_Listeners, const FVector& SourcePosition, const AActor* Actor, AkRoomID RoomID, ECollisionChannel in_collisionChannel, float DeltaTime, float OcclusionRefreshInterval)
  356. {
  357. // Check Occlusion/Obstruction, if enabled
  358. if (OcclusionRefreshInterval > 0.0f || ClearingObstructionAndOcclusion)
  359. {
  360. RefreshObstructionAndOcclusion(in_Listeners, SourcePosition, Actor, RoomID, in_collisionChannel, DeltaTime, OcclusionRefreshInterval);
  361. }
  362. else if (OcclusionRefreshInterval != PreviousRefreshInterval)
  363. {
  364. // Reset the occlusion obstruction pairs so that the occlusion is correctly recalculated.
  365. for (auto& ListenerPack : ListenerInfoMap)
  366. {
  367. FAkListenerObstructionAndOcclusionPair& Pair = ListenerPack.Value;
  368. Pair.Reset();
  369. }
  370. if (OcclusionRefreshInterval <= 0.0f)
  371. ClearOcclusionValues();
  372. }
  373. PreviousRefreshInterval = OcclusionRefreshInterval;
  374. }
  375. void AkObstructionAndOcclusionService::UpdateObstructionAndOcclusion(const UAkComponentSet& in_Listeners, const FVector& SourcePosition, const AActor* Actor, AkRoomID RoomID, ECollisionChannel in_collisionChannel, float OcclusionRefreshInterval)
  376. {
  377. if ((OcclusionRefreshInterval > 0.f || ClearingObstructionAndOcclusion) && Actor)
  378. {
  379. for (auto& Listener : in_Listeners)
  380. {
  381. auto& MapEntry = ListenerInfoMap.FindOrAdd(Listener->GetAkGameObjectID());
  382. MapEntry.Position = Listener->GetPosition();
  383. }
  384. CalculateObstructionAndOcclusionValues(in_Listeners, SourcePosition, Actor, RoomID, in_collisionChannel, false);
  385. for (auto& ListenerPair : ListenerInfoMap)
  386. {
  387. ListenerPair.Value.Obs.CurrentValue = ListenerPair.Value.Obs.TargetValue;
  388. ListenerPair.Value.Occ.CurrentValue = ListenerPair.Value.Occ.TargetValue;
  389. }
  390. SetObstructionAndOcclusion(in_Listeners, RoomID);
  391. }
  392. }