AkAcousticPortal.cpp 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026
  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. AAkAcousticPortal.cpp:
  17. =============================================================================*/
  18. #include "AkAcousticPortal.h"
  19. #include "AkAudioDevice.h"
  20. #include "AkComponentHelpers.h"
  21. #include "AkSpatialAudioHelper.h"
  22. #include "AkSpatialAudioDrawUtils.h"
  23. #include "Components/BrushComponent.h"
  24. #include "Model.h"
  25. #include "EngineUtils.h"
  26. #include "AkRoomComponent.h"
  27. #include "AkComponent.h"
  28. #include "AkCustomVersion.h"
  29. #include "Kismet/KismetMathLibrary.h"
  30. #include "AkSpatialAudioVolume.h"
  31. // A standard AAkAcousticPortal is based on a cube brush with verts at [+/-]100 X,Y,Z.
  32. static const float kDefaultBrushExtents = 100.f;
  33. // min portal size, in cm. For raycasts
  34. static const float kMinPortalSize = 10.0f;
  35. #if WITH_EDITOR
  36. #include "AkDrawPortalComponent.h"
  37. #include "AkAudioStyle.h"
  38. #include "LevelEditorViewport.h"
  39. #endif
  40. UAkPortalComponent::UAkPortalComponent(const class FObjectInitializer& ObjectInitializer) :
  41. Super(ObjectInitializer)
  42. {
  43. ObstructionRefreshInterval = 0.f;
  44. PortalState = InitialState;
  45. bUseAttachParentBound = true;
  46. FrontRoom = nullptr;
  47. BackRoom = nullptr;
  48. PrimaryComponentTick.bCanEverTick = true;
  49. PrimaryComponentTick.bStartWithTickEnabled = true;
  50. bTickInEditor = true;
  51. #if WITH_EDITOR
  52. bWantsOnUpdateTransform = true;
  53. bWantsInitializeComponent = true;
  54. #else
  55. bWantsOnUpdateTransform = bDynamic;
  56. #endif
  57. #if WITH_EDITOR
  58. if (AkSpatialAudioHelper::GetObjectReplacedEvent())
  59. {
  60. AkSpatialAudioHelper::GetObjectReplacedEvent()->AddUObject(this, &UAkPortalComponent::HandleObjectsReplaced);
  61. }
  62. #endif
  63. }
  64. void UAkPortalComponent::OnRegister()
  65. {
  66. Super::OnRegister();
  67. SetRelativeTransform(FTransform::Identity);
  68. InitializeParent();
  69. UpdateConnectedRooms();
  70. UWorld* world = GetWorld();
  71. if (world != nullptr)
  72. SetSpatialAudioPortal();
  73. #if WITH_EDITOR
  74. if (GetDefault<UAkSettings>()->VisualizeRoomsAndPortals)
  75. {
  76. InitializeDrawComponent();
  77. }
  78. #endif
  79. }
  80. void UAkPortalComponent::OnUnregister()
  81. {
  82. Super::OnUnregister();
  83. #if WITH_EDITOR
  84. if (!HasAnyFlags(RF_Transient))
  85. {
  86. DestroyTextVisualizers();
  87. }
  88. #endif
  89. FAkAudioDevice * Dev = FAkAudioDevice::Get();
  90. if (Dev != nullptr)
  91. {
  92. Dev->RemoveSpatialAudioPortal(this);
  93. }
  94. }
  95. #if WITH_EDITOR
  96. void UAkPortalComponent::BeginDestroy()
  97. {
  98. Super::BeginDestroy();
  99. if (AkSpatialAudioHelper::GetObjectReplacedEvent())
  100. {
  101. AkSpatialAudioHelper::GetObjectReplacedEvent()->RemoveAll(this);
  102. }
  103. }
  104. void UAkPortalComponent::HandleObjectsReplaced(const TMap<UObject*, UObject*>& ReplacementMap)
  105. {
  106. if (ReplacementMap.Contains(Parent))
  107. {
  108. InitializeParent();
  109. }
  110. if (ReplacementMap.Contains(FrontRoom) || ReplacementMap.Contains(BackRoom))
  111. {
  112. UpdateConnectedRooms();
  113. }
  114. }
  115. void UAkPortalComponent::InitializeComponent()
  116. {
  117. Super::InitializeComponent();
  118. RegisterVisEnabledCallback();
  119. }
  120. void UAkPortalComponent::OnComponentCreated()
  121. {
  122. Super::OnComponentCreated();
  123. RegisterVisEnabledCallback();
  124. }
  125. void UAkPortalComponent::PostLoad()
  126. {
  127. Super::PostLoad();
  128. RegisterVisEnabledCallback();
  129. }
  130. void UAkPortalComponent::OnComponentDestroyed(bool bDestroyingHierarchy)
  131. {
  132. UAkSettings* AkSettings = GetMutableDefault<UAkSettings>();
  133. AkSettings->OnShowRoomsPortalsChanged.Remove(ShowPortalsChangedHandle);
  134. ShowPortalsChangedHandle.Reset();
  135. DestroyDrawComponent();
  136. }
  137. #endif // WITH_EDITOR
  138. bool UAkPortalComponent::MoveComponentImpl(
  139. const FVector & Delta,
  140. const FQuat & NewRotation,
  141. bool bSweep,
  142. FHitResult * Hit,
  143. EMoveComponentFlags MoveFlags,
  144. ETeleportType Teleport)
  145. {
  146. if (AkComponentHelpers::DoesMovementRecenterChild(this, Parent, Delta))
  147. Super::MoveComponentImpl(Delta, NewRotation, bSweep, Hit, MoveFlags, Teleport);
  148. return false;
  149. }
  150. void UAkPortalComponent::OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport)
  151. {
  152. PortalNeedUpdated = true;
  153. #if WITH_EDITOR
  154. UpdateTextLocRotVis();
  155. #endif
  156. }
  157. #if WITH_EDITOR
  158. void UAkPortalComponent::RegisterVisEnabledCallback()
  159. {
  160. if (!ShowPortalsChangedHandle.IsValid())
  161. {
  162. UAkSettings* AkSettings = GetMutableDefault<UAkSettings>();
  163. ShowPortalsChangedHandle = AkSettings->OnShowRoomsPortalsChanged.AddLambda([this, AkSettings]()
  164. {
  165. if (AkSettings->VisualizeRoomsAndPortals)
  166. {
  167. InitializeDrawComponent();
  168. }
  169. else
  170. {
  171. DestroyDrawComponent();
  172. }
  173. });
  174. }
  175. }
  176. void UAkPortalComponent::InitializeDrawComponent()
  177. {
  178. if (AActor* Owner = GetOwner())
  179. {
  180. if (DrawPortalComponent == nullptr)
  181. {
  182. DrawPortalComponent = NewObject<UDrawPortalComponent>(Owner, NAME_None, RF_Transactional | RF_TextExportTransient);
  183. DrawPortalComponent->SetupAttachment(this);
  184. DrawPortalComponent->SetIsVisualizationComponent(true);
  185. DrawPortalComponent->CreationMethod = CreationMethod;
  186. DrawPortalComponent->RegisterComponentWithWorld(GetWorld());
  187. DrawPortalComponent->MarkRenderStateDirty();
  188. }
  189. }
  190. }
  191. void UAkPortalComponent::DestroyDrawComponent()
  192. {
  193. if (DrawPortalComponent != nullptr)
  194. {
  195. DrawPortalComponent->DestroyComponent();
  196. DrawPortalComponent = nullptr;
  197. }
  198. }
  199. #endif
  200. void UAkPortalComponent::InitializeParent()
  201. {
  202. USceneComponent* SceneParent = GetAttachParent();
  203. if (SceneParent != nullptr)
  204. {
  205. Parent = Cast<UPrimitiveComponent>(SceneParent);
  206. if (!Parent)
  207. {
  208. AkComponentHelpers::LogAttachmentError(this, SceneParent, "UPrimitiveComponent");
  209. }
  210. #if WITH_EDITOR
  211. DestroyTextVisualizers();
  212. InitTextVisualizers();
  213. UpdateRoomNames();
  214. UpdateTextLocRotVis();
  215. #endif
  216. }
  217. }
  218. void UAkPortalComponent::SetSpatialAudioPortal()
  219. {
  220. FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get();
  221. if (AkAudioDevice != nullptr)
  222. {
  223. AkAudioDevice->SetSpatialAudioPortal(this);
  224. PortalNeedUpdated = false;
  225. }
  226. }
  227. void UAkPortalComponent::OpenPortal()
  228. {
  229. if (PortalState == AkAcousticPortalState::Closed)
  230. {
  231. PortalState = AkAcousticPortalState::Open;
  232. SetSpatialAudioPortal();
  233. }
  234. }
  235. void UAkPortalComponent::ClosePortal()
  236. {
  237. if (PortalState == AkAcousticPortalState::Open)
  238. {
  239. PortalState = AkAcousticPortalState::Closed;
  240. SetSpatialAudioPortal();
  241. }
  242. }
  243. AkAcousticPortalState UAkPortalComponent::GetCurrentState() const
  244. {
  245. return PortalState;
  246. }
  247. void UAkPortalComponent::BeginPlay()
  248. {
  249. Super::BeginPlay();
  250. // If we're PIE, or somehow otherwise in a game world in editor, simulate the bDynamic behaviour.
  251. #if WITH_EDITOR
  252. UWorld* world = GetWorld();
  253. if (world != nullptr && (world->WorldType == EWorldType::Type::Game || world->WorldType == EWorldType::Type::PIE))
  254. bWantsOnUpdateTransform = bDynamic;
  255. #endif
  256. UpdateConnectedRooms();
  257. ResetPortalState();
  258. FAkAudioDevice * Dev = FAkAudioDevice::Get();
  259. if (Dev != nullptr)
  260. {
  261. ObstructionService.Init(this, ObstructionRefreshInterval);
  262. }
  263. }
  264. void UAkPortalComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction * ThisTickFunction)
  265. {
  266. if (PortalNeedUpdated)
  267. {
  268. UpdateConnectedRooms();
  269. SetSpatialAudioPortal();
  270. }
  271. if (GetCurrentState() == AkAcousticPortalState::Open)
  272. {
  273. FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get();
  274. if (AkAudioDevice)
  275. {
  276. UAkComponent* Listener = AkAudioDevice->GetSpatialAudioListener();
  277. if (Listener != nullptr)
  278. {
  279. AkRoomID listenerRoom = Listener->GetSpatialAudioRoom();
  280. UAkComponentSet set;
  281. set.Add(Listener);
  282. ObstructionService.Tick(set, GetOwner()->GetActorLocation(), GetOwner(), listenerRoom, ObstructionCollisionChannel, DeltaTime, ObstructionRefreshInterval);
  283. }
  284. }
  285. }
  286. #if WITH_EDITOR
  287. UWorld* World = GetWorld();
  288. if (World && (World->WorldType == EWorldType::Editor || World->WorldType == EWorldType::PIE))
  289. {
  290. // Only show the text renderer for selected actors.
  291. if (GetOwner()->IsSelected() && !bWasSelected)
  292. {
  293. bWasSelected = true;
  294. UpdateTextVisibility();
  295. }
  296. if (!GetOwner()->IsSelected() && bWasSelected)
  297. {
  298. bWasSelected = false;
  299. UpdateTextVisibility();
  300. }
  301. }
  302. #endif
  303. }
  304. void UAkPortalComponent::ResetPortalState()
  305. {
  306. PortalState = InitialState;
  307. SetSpatialAudioPortal();
  308. }
  309. bool UAkPortalComponent::UpdateConnectedRooms()
  310. {
  311. /* Keep note of the rooms and validity before the update. */
  312. AkRoomID previousFront = GetFrontRoom();
  313. AkRoomID previousBack = GetBackRoom();
  314. /* Update the room connections */
  315. FrontRoom = nullptr;
  316. BackRoom = nullptr;
  317. FAkAudioDevice * Dev = FAkAudioDevice::Get();
  318. FindConnectedComponents(Dev->GetRoomIndex(), FrontRoom, BackRoom);
  319. LastRoomsUpdate = GetWorld()->GetTimeSeconds();
  320. PreviousLocation = GetComponentLocation();
  321. PreviousRotation = GetComponentRotation();
  322. const bool bRoomsChanged = GetFrontRoom() != previousFront || GetBackRoom() != previousBack;
  323. #if WITH_EDITOR
  324. if (bRoomsChanged)
  325. UpdateRoomNames();
  326. UpdateTextLocRotVis();
  327. #endif
  328. /* Return true if any room connection has changed. */
  329. return bRoomsChanged;
  330. }
  331. UPrimitiveComponent* UAkPortalComponent::GetPrimitiveParent() const
  332. {
  333. return Parent;
  334. }
  335. FVector UAkPortalComponent::GetExtent() const
  336. {
  337. FBoxSphereBounds ComponentBounds = Bounds;
  338. if (Parent != nullptr)
  339. {
  340. FTransform Transform (Parent->GetComponentTransform());
  341. Transform.SetRotation(FQuat::Identity);
  342. ComponentBounds = Parent->CalcBounds(Transform);
  343. }
  344. return ComponentBounds.BoxExtent;
  345. }
  346. AkRoomID UAkPortalComponent::GetFrontRoom() const { return FrontRoom == nullptr ? AkRoomID() : FrontRoom->GetRoomID(); }
  347. AkRoomID UAkPortalComponent::GetBackRoom() const { return BackRoom == nullptr ? AkRoomID() : BackRoom->GetRoomID(); }
  348. template <typename tComponent>
  349. void UAkPortalComponent::FindConnectedComponents(FAkEnvironmentIndex& RoomIndex, tComponent*& out_pFront, tComponent*& out_pBack)
  350. {
  351. out_pFront = nullptr;
  352. out_pBack = nullptr;
  353. FAkAudioDevice* pAudioDevice = FAkAudioDevice::Get();
  354. if (pAudioDevice != nullptr && Parent != nullptr)
  355. {
  356. float x = GetExtent().X;
  357. FVector frontVector(x, 0.f, 0.f);
  358. FTransform toWorld = Parent->GetComponentTransform();
  359. toWorld.SetScale3D(FVector(1.0f));
  360. FVector frontPoint = toWorld.TransformPosition(frontVector);
  361. FVector backPoint = toWorld.TransformPosition(-1 * frontVector);
  362. TArray<tComponent*> front = RoomIndex.Query<tComponent>(frontPoint, GetWorld());
  363. if (front.Num() > 0)
  364. out_pFront = front[0];
  365. TArray<tComponent*> back = RoomIndex.Query<tComponent>(backPoint, GetWorld());
  366. if (back.Num() > 0)
  367. out_pBack = back[0];
  368. }
  369. }
  370. #if WITH_EDITOR
  371. bool UAkPortalComponent::AreTextVisualizersInitialized() const
  372. {
  373. return FrontRoomText != nullptr || BackRoomText != nullptr;
  374. }
  375. void UAkPortalComponent::InitTextVisualizers()
  376. {
  377. if (!HasAnyFlags(RF_Transient))
  378. {
  379. FString NamePrefix = GetOwner()->GetName() + GetName();
  380. UMaterialInterface* mat = Cast<UMaterialInterface>(FAkAudioStyle::GetAkForegroundTextMaterial());
  381. FrontRoomText = NewObject<UTextRenderComponent>(GetOuter(), *(NamePrefix + "_FrontRoomName"));
  382. BackRoomText = NewObject<UTextRenderComponent>(GetOuter(), *(NamePrefix + "_BackRoomName"));
  383. FrontRoomText->SetText(FText::FromString(""));
  384. BackRoomText->SetText(FText::FromString(""));
  385. TArray<UTextRenderComponent*> TextComponents{ FrontRoomText, BackRoomText };
  386. for (UTextRenderComponent* Text : TextComponents)
  387. {
  388. if (mat != nullptr)
  389. Text->SetTextMaterial(mat);
  390. Text->RegisterComponentWithWorld(GetWorld());
  391. Text->AttachToComponent(this, FAttachmentTransformRules::KeepWorldTransform);
  392. Text->SetRelativeLocation(FVector(0.0f, 0.0f, 0.0f));
  393. Text->bIsEditorOnly = true;
  394. Text->bSelectable = true;
  395. Text->bAlwaysRenderAsText = true;
  396. Text->SetHorizontalAlignment(EHTA_Center);
  397. Text->SetWorldScale3D(FVector(1.0f));
  398. }
  399. FrontRoomText->SetVerticalAlignment(EVRTA_TextTop);
  400. BackRoomText->SetVerticalAlignment(EVRTA_TextBottom);
  401. }
  402. }
  403. void UAkPortalComponent::DestroyTextVisualizers()
  404. {
  405. if (FrontRoomText != nullptr)
  406. {
  407. FrontRoomText->DestroyComponent();
  408. FrontRoomText = nullptr;
  409. }
  410. if (BackRoomText != nullptr)
  411. {
  412. BackRoomText->DestroyComponent();
  413. BackRoomText = nullptr;
  414. }
  415. }
  416. void UAkPortalComponent::UpdateRoomNames()
  417. {
  418. if (!Parent || HasAnyFlags(RF_Transient) || !AreTextVisualizersInitialized())
  419. return;
  420. if (FrontRoomText != nullptr)
  421. {
  422. FrontRoomText->SetText(FText::FromString(""));
  423. if (FrontRoom != nullptr)
  424. FrontRoomText->SetText(FText::FromString(FrontRoom->GetRoomName()));
  425. }
  426. if (BackRoomText != nullptr)
  427. {
  428. BackRoomText->SetText(FText::FromString(""));
  429. if (BackRoom != nullptr)
  430. BackRoomText->SetText(FText::FromString(BackRoom->GetRoomName()));
  431. }
  432. }
  433. void UAkPortalComponent::UpdateTextRotations() const
  434. {
  435. if (Parent == nullptr || !AreTextVisualizersInitialized())
  436. return;
  437. FVector BoxExtent = Parent->CalcBounds(FTransform()).BoxExtent;
  438. const FTransform T = Parent->GetComponentTransform();
  439. AkDrawBounds DrawBounds(T, BoxExtent);
  440. // Setup the font normal to orient the text.
  441. FVector Front = DrawBounds.FLD() - DrawBounds.BLD();
  442. Front.Normalize();
  443. FVector Up = DrawBounds.FLU() - DrawBounds.FLD();
  444. Up.Normalize();
  445. if (FrontRoomText != nullptr)
  446. FrontRoomText->SetVerticalAlignment(EVRTA_TextTop);
  447. if (BackRoomText != nullptr)
  448. BackRoomText->SetVerticalAlignment(EVRTA_TextBottom);
  449. if (FVector::DotProduct(FVector::UpVector, Up) < 0.0f)
  450. {
  451. if (FrontRoomText != nullptr)
  452. FrontRoomText->SetVerticalAlignment(EVRTA_TextBottom);
  453. if (BackRoomText != nullptr)
  454. BackRoomText->SetVerticalAlignment(EVRTA_TextTop);
  455. Up *= -1.0f;
  456. }
  457. // Choose to point both text components towards the front or back of the portal, depending on the position of the camera, so that they are both always readable.
  458. FVector CamToCentre;
  459. if (GCurrentLevelEditingViewportClient != nullptr)
  460. {
  461. CamToCentre = GCurrentLevelEditingViewportClient->GetViewLocation() - Parent->Bounds.Origin;
  462. if (FVector::DotProduct(CamToCentre, Front) < 0.0f)
  463. Front *= -1.0f;
  464. }
  465. if (FrontRoomText != nullptr)
  466. FrontRoomText->SetWorldRotation(UKismetMathLibrary::MakeRotFromXZ(Front, Up));
  467. if (BackRoomText != nullptr)
  468. BackRoomText->SetWorldRotation(UKismetMathLibrary::MakeRotFromXZ(Front, Up));
  469. }
  470. void UAkPortalComponent::UpdateTextVisibility()
  471. {
  472. if (!AreTextVisualizersInitialized())
  473. return;
  474. bool Visible = false;
  475. if (GetWorld() != nullptr)
  476. {
  477. EWorldType::Type WorldType = GetWorld()->WorldType;
  478. if (WorldType == EWorldType::Editor)
  479. {
  480. Visible = GetOwner() != nullptr && GetOwner()->IsSelected();
  481. }
  482. else if (WorldType == EWorldType::EditorPreview)
  483. {
  484. Visible = true;
  485. }
  486. }
  487. if (GetOwner() != nullptr)
  488. {
  489. if (BackRoomText != nullptr)
  490. BackRoomText->SetVisibility(Visible);
  491. if (FrontRoomText != nullptr)
  492. FrontRoomText->SetVisibility(Visible);
  493. }
  494. }
  495. void UAkPortalComponent::UpdateTextLocRotVis()
  496. {
  497. if (Parent == nullptr || !AreTextVisualizersInitialized())
  498. return;
  499. FVector BoxExtent = Parent->CalcBounds(FTransform()).BoxExtent;
  500. const FTransform T = Parent->GetComponentTransform();
  501. AkDrawBounds DrawBounds(T, BoxExtent);
  502. float PortalWidth = 0.0f;
  503. FVector Right;
  504. (DrawBounds.FRD() - DrawBounds.FLD()).ToDirectionAndLength(Right, PortalWidth);
  505. // Setup the font normal to orient the text.
  506. FVector Front = DrawBounds.FLD() - DrawBounds.BLD();
  507. FVector Up = DrawBounds.FLU() - DrawBounds.FLD();
  508. if (FrontRoomText != nullptr)
  509. {
  510. FrontRoomText->SetWorldScale3D(FVector(1.0f));
  511. // Get a point at the top center of the local axis-aligned bounds, to position the front room text.
  512. //FVector Top = Parent->Bounds.Origin + FVector(0.0f, 0.0f, Parent->Bounds.BoxExtent.Z);
  513. FVector Top = Parent->Bounds.Origin;// +Front + Up;
  514. // Add a depth offset so that the text sits out at the top front of the portal
  515. Top += Front * 0.5f;
  516. Top += Up * 0.5f;
  517. FrontRoomText->SetWorldLocation(Top);
  518. const FVector FrontTextWorldSize = FrontRoomText->GetTextWorldSize();
  519. const float TextWidth = FrontTextWorldSize.GetAbsMax();
  520. if (TextWidth > PortalWidth && PortalWidth > 0.0f)
  521. FrontRoomText->SetWorldScale3D(FVector(PortalWidth / TextWidth));
  522. }
  523. if (BackRoomText != nullptr)
  524. {
  525. BackRoomText->SetWorldScale3D(FVector(1.0f));
  526. // Get a point at the bottom center of the local axis-aligned bounds, to position the back room text.
  527. //FVector Bottom = Parent->Bounds.Origin - FVector(0.0f, 0.0f, Parent->Bounds.BoxExtent.Z);
  528. FVector Bottom = Parent->Bounds.Origin;
  529. // Add a depth offset so that the text sits out at the bottom back of the portal
  530. Bottom -= Front * 0.5f;
  531. Bottom -= Up * 0.5f;
  532. BackRoomText->SetWorldLocation(Bottom);
  533. const FVector BackTextWorldSize = BackRoomText->GetTextWorldSize();
  534. const float TextWidth = BackTextWorldSize.GetAbsMax();
  535. if (TextWidth > PortalWidth && PortalWidth > 0.0f)
  536. BackRoomText->SetWorldScale3D(FVector(PortalWidth / TextWidth));
  537. }
  538. UpdateTextRotations();
  539. UpdateTextVisibility();
  540. }
  541. #endif
  542. /*------------------------------------------------------------------------------------
  543. AAkAcousticPortal
  544. ------------------------------------------------------------------------------------*/
  545. AAkAcousticPortal::AAkAcousticPortal(const class FObjectInitializer& ObjectInitializer) :
  546. Super(ObjectInitializer)
  547. {
  548. // Property initialization
  549. static const FName CollisionProfileName(TEXT("OverlapAll"));
  550. GetBrushComponent()->SetCollisionProfileName(CollisionProfileName);
  551. bColored = true;
  552. BrushColor = FColor(255, 196, 137, 255);
  553. InitialState = AkAcousticPortalState::Open;
  554. PrimaryActorTick.bCanEverTick = true;
  555. PrimaryActorTick.TickGroup = TG_DuringPhysics;
  556. PrimaryActorTick.bAllowTickOnDedicatedServer = false;
  557. static const FName PortalComponentName = TEXT("PortalComponent");
  558. Portal = ObjectInitializer.CreateDefaultSubobject<UAkPortalComponent>(this, PortalComponentName);
  559. Portal->AttachToComponent(GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
  560. #if WITH_EDITOR
  561. CollisionChannel = EAkCollisionChannel::EAKCC_UseIntegrationSettingsDefault;
  562. #endif
  563. }
  564. void AAkAcousticPortal::OpenPortal()
  565. {
  566. if (Portal != nullptr)
  567. {
  568. Portal->OpenPortal();
  569. }
  570. else
  571. {
  572. UE_LOG(LogAkAudio, Warning, TEXT("AAkAcousticPortal %s called OpenPortal with uninitialized portal component."), *GetName());
  573. }
  574. }
  575. void AAkAcousticPortal::ClosePortal()
  576. {
  577. if (Portal != nullptr)
  578. {
  579. Portal->ClosePortal();
  580. }
  581. else
  582. {
  583. UE_LOG(LogAkAudio, Warning, TEXT("AAkAcousticPortal %s called ClosePortal with uninitialized portal component."), *GetName());
  584. }
  585. }
  586. AkAcousticPortalState AAkAcousticPortal::GetCurrentState() const
  587. {
  588. if (Portal != nullptr)
  589. return Portal->GetCurrentState();
  590. UE_LOG(LogAkAudio, Warning, TEXT("AAkAcousticPortal %s called GetCurrentState with uninitialized portal component."), *GetName());
  591. return AkAcousticPortalState::Closed;
  592. }
  593. AkRoomID AAkAcousticPortal::GetFrontRoom() const
  594. {
  595. if (Portal != nullptr)
  596. return Portal->GetFrontRoom();
  597. UE_LOG(LogAkAudio, Warning, TEXT("AAkAcousticPortal %s called GetFrontRoom with uninitialized portal component."), *GetName());
  598. return AkRoomID();
  599. }
  600. AkRoomID AAkAcousticPortal::GetBackRoom() const
  601. {
  602. if (Portal != nullptr)
  603. return Portal->GetBackRoom();
  604. UE_LOG(LogAkAudio, Warning, TEXT("AAkAcousticPortal %s called GetBackRoom with uninitialized portal component."), *GetName());
  605. return AkRoomID();
  606. }
  607. void AAkAcousticPortal::PostRegisterAllComponents()
  608. {
  609. Super::PostRegisterAllComponents();
  610. if (bRequiresStateMigration)
  611. {
  612. if (Portal != nullptr)
  613. {
  614. Portal->InitialState = InitialState;
  615. bRequiresStateMigration = false;
  616. }
  617. }
  618. if (bRequiresTransformMigration)
  619. {
  620. FVector right = FVector(0.0f, 1.0f, 0.0f);
  621. FVector left = FVector(0.0f, -1.0f, 0.0f);
  622. FTransform actorTransform = GetActorTransform();
  623. /* get the local 'front' (with respect to Y). */
  624. FVector localYFront = (actorTransform.TransformPosition(right) - actorTransform.TransformPosition(left));
  625. localYFront.Normalize();
  626. FVector scale = GetActorScale3D();
  627. SetActorScale3D(FVector(scale.Y, scale.X, scale.Z));
  628. /* get the local front, using Unreal coordinate orientation. */
  629. FVector localXFront = GetActorForwardVector();
  630. /* get the local up vector around which to rotate. */
  631. FVector localUp = FVector::CrossProduct(localYFront, localXFront);
  632. /* rotate the local front vector around the local up, such that it points along the 'true' local front, in Unreal terms. */
  633. localXFront = localXFront.RotateAngleAxis(-90.0f, localUp);
  634. /* Set up new local axes such that localUp remains constant, local front is changed to localXFront, and the local right is calculated from these two. */
  635. SetActorRotation(UKismetMathLibrary::MakeRotFromXZ(localXFront, localUp));
  636. bRequiresTransformMigration = false;
  637. }
  638. }
  639. void AAkAcousticPortal::PostLoad()
  640. {
  641. Super::PostLoad();
  642. const int32 AkVersion = GetLinkerCustomVersion(FAkCustomVersion::GUID);
  643. if (AkVersion < FAkCustomVersion::SpatialAudioExtentAPIChange)
  644. {
  645. bRequiresTransformMigration = true;
  646. }
  647. if (AkVersion < FAkCustomVersion::SpatialAudioComponentisation)
  648. {
  649. bRequiresStateMigration = true;
  650. }
  651. }
  652. void AAkAcousticPortal::Serialize(FArchive& Ar)
  653. {
  654. Ar.UsingCustomVersion(FAkCustomVersion::GUID);
  655. Super::Serialize(Ar);
  656. }
  657. #if WITH_EDITOR
  658. ECollisionChannel AAkAcousticPortal::GetCollisionChannel()
  659. {
  660. return UAkSettings::ConvertFitToGeomCollisionChannel(CollisionChannel.GetValue());
  661. }
  662. void AAkAcousticPortal::FitRaycast()
  663. {
  664. static const FName NAME_SAV_Fit = TEXT("AAkAcousticPortalRaycast");
  665. UWorld* World = GEngine->GetWorldFromContextObjectChecked(this);
  666. if (!World)
  667. return;
  668. TArray<TTuple<float, FVector, FVector>> hits;
  669. // Ray length - DetectionRadius X current scale.
  670. float RayLength = GetDetectionRadius();
  671. FCollisionQueryParams CollisionParams(NAME_SAV_Fit, true, this);
  672. FVector RaycastOrigin = bUseSavedRaycastOrigin ? SavedRaycastOrigin : GetActorLocation();
  673. float Offset = 2.f / kNumRaycasts;
  674. float Increment = PI * (3.f - sqrtf(5.f));
  675. TArray< FHitResult > OutHits;
  676. for (int i = 0; i < kNumRaycasts; ++i)
  677. {
  678. float x = ((i * Offset) - 1) + (Offset / 2);
  679. float r = sqrtf(1.f - powf(x, 2.f));
  680. float phi = ((i + 1) % kNumRaycasts) * Increment;
  681. float y = cosf(phi) * r;
  682. float z = sinf(phi) * r;
  683. FVector to = RaycastOrigin + FVector(x, y, z) * RayLength;
  684. OutHits.Empty();
  685. World->LineTraceMultiByObjectType(OutHits, RaycastOrigin, to, (int)GetCollisionChannel(), CollisionParams);
  686. if (OutHits.Num() > 0)
  687. {
  688. bool bHit = false;
  689. FVector ImpactPoint0;
  690. FVector ImpactNormal0;
  691. for (auto& res : OutHits)
  692. {
  693. if (res.IsValidBlockingHit() &&
  694. !AkSpatialAudioHelper::IsAkSpatialAudioActorClass(AkSpatialAudioHelper::GetActorFromHitResult(res)))
  695. {
  696. bHit = true;
  697. ImpactPoint0 = res.ImpactPoint;
  698. ImpactNormal0 = res.ImpactNormal;
  699. break;
  700. }
  701. }
  702. if (bHit)
  703. {
  704. OutHits.Empty();
  705. World->LineTraceMultiByObjectType(OutHits, ImpactPoint0, ImpactPoint0 + ImpactNormal0 * RayLength, (int)GetCollisionChannel(), CollisionParams);
  706. bHit = false;
  707. FVector ImpactPoint1;
  708. for (auto& res : OutHits)
  709. {
  710. if (res.IsValidBlockingHit() &&
  711. res.Distance > kMinPortalSize &&
  712. !AkSpatialAudioHelper::IsAkSpatialAudioActorClass(AkSpatialAudioHelper::GetActorFromHitResult(res)))
  713. {
  714. bHit = true;
  715. ImpactPoint1 = res.ImpactPoint;
  716. break;
  717. }
  718. }
  719. if (bHit)
  720. {
  721. float distance = (ImpactPoint0 - ImpactPoint1).Size();
  722. hits.Emplace(MakeTuple(distance, ImpactPoint0, ImpactPoint1));
  723. }
  724. }
  725. }
  726. }
  727. auto SortPredicate = [](TTuple<float, FVector, FVector>& A, TTuple<float, FVector, FVector>& B) { return A.Get<0>() < B.Get<0>(); };
  728. Algo::Sort(hits, SortPredicate);
  729. static const float kDotEpsilon = 0.1f;
  730. static const float kLineIntersectThresh = 2.0f;
  731. float minDist = FLT_MAX;
  732. int Best0 = INT_MAX;
  733. int Best1 = INT_MAX;
  734. bool bIntersects = false;
  735. for (int i = 0; i < hits.Num() && !bIntersects; ++i)
  736. {
  737. FVector& pti = hits[i].Get<1>();
  738. FVector vi = hits[i].Get<2>() - hits[i].Get<1>();
  739. FVector diri;
  740. float leni;
  741. vi.ToDirectionAndLength(diri, leni);
  742. for (int j = i + 1; j < hits.Num() && !bIntersects; ++j)
  743. {
  744. FVector& ptj = hits[j].Get<1>();
  745. FVector vj = hits[j].Get<2>() - hits[j].Get<1>();
  746. FVector dirj;
  747. float lenj;
  748. vj.ToDirectionAndLength(dirj, lenj);
  749. if (FMath::Abs(FVector::DotProduct(diri, dirj)) < kDotEpsilon)
  750. {
  751. float proj_ji = FVector::DotProduct((ptj - pti), diri);
  752. if (proj_ji > 0.f && proj_ji < leni)
  753. {
  754. float proj_ij = FVector::DotProduct((pti - ptj), dirj);
  755. if (proj_ij > 0.f && proj_ij < lenj)
  756. {
  757. FVector p0 = pti + proj_ji * diri;
  758. FVector p1 = ptj + proj_ij * dirj;
  759. float dist = (p0 - p1).Size();
  760. if (dist < minDist)
  761. {
  762. minDist = dist;
  763. Best0 = i;
  764. Best1 = j;
  765. if (dist < kLineIntersectThresh)
  766. {
  767. //Assuming here we found a pretty good result, bail out so as to favor smaller portals over bigger ones.
  768. bIntersects = true;
  769. }
  770. }
  771. }
  772. }
  773. }
  774. }
  775. }
  776. if (bIntersects)
  777. {
  778. BestFit[0] = hits[Best0].Get<1>();
  779. BestFit[1] = hits[Best0].Get<2>();
  780. BestFit[2] = hits[Best1].Get<1>();
  781. BestFit[3] = hits[Best1].Get<2>();
  782. BestFitValid = true;
  783. }
  784. else
  785. {
  786. // We will hold on to the best fit points, as long as they are within the detection radius.
  787. BestFitValid = FVector::DistSquared(RaycastOrigin, (BestFit[0] + BestFit[1] + BestFit[2] + BestFit[3]) / 4.f) < DetectionRadius * DetectionRadius;
  788. }
  789. }
  790. void AAkAcousticPortal::FitPortal()
  791. {
  792. if (!BestFitValid)
  793. return;
  794. FVector center;
  795. FVector front;
  796. FVector side;
  797. FVector up;
  798. FVector scale;
  799. FVector& pti = BestFit[0];
  800. FVector vi = BestFit[1] - BestFit[0];
  801. FVector diri;
  802. float leni;
  803. vi.ToDirectionAndLength(diri, leni);
  804. FVector& ptj = BestFit[2];
  805. FVector vj = BestFit[3] - BestFit[2];
  806. FVector dirj;
  807. float lenj;
  808. vj.ToDirectionAndLength(dirj, lenj);
  809. float proj_ji = FVector::DotProduct((ptj - pti), diri);
  810. if (proj_ji > 0.f && proj_ji < leni)
  811. {
  812. float proj_ij = FVector::DotProduct((pti - ptj), dirj);
  813. if (proj_ij > 0.f && proj_ij < lenj)
  814. {
  815. FVector p0 = pti + proj_ji * diri;
  816. FVector p1 = ptj + proj_ij * dirj;
  817. center = pti - proj_ij * dirj;
  818. center += diri * leni / 2.f + dirj * lenj / 2.f;
  819. front = FVector::CrossProduct(diri, dirj);
  820. side = diri;
  821. up = dirj;
  822. scale.Y = leni / 2.f;
  823. scale.Z = lenj / 2.f;
  824. scale /= kDefaultBrushExtents;
  825. scale.X = GetActorScale3D().X;
  826. auto* RC = GetRootComponent();
  827. if (RC)
  828. {
  829. RC->SetWorldLocation(center);
  830. FRotator rotation = FRotationMatrix::MakeFromXZ(front, up).Rotator();
  831. RC->SetWorldRotation(rotation);
  832. RC->SetWorldScale3D(scale);
  833. }
  834. }
  835. }
  836. }
  837. void AAkAcousticPortal::PostEditMove(bool bFinished)
  838. {
  839. Super::PostEditMove(bFinished);
  840. if (FitToGeometry)
  841. {
  842. FitRaycast();
  843. IsDragging = !bFinished;
  844. if (bFinished)
  845. {
  846. bUseSavedRaycastOrigin = false;
  847. FitPortal();
  848. }
  849. }
  850. }
  851. void AAkAcousticPortal::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
  852. {
  853. Super::PostEditChangeProperty(PropertyChangedEvent);
  854. if (PropertyChangedEvent.Property)
  855. {
  856. if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(AAkAcousticPortal, FitToGeometry))
  857. {
  858. ClearBestFit();
  859. if (FitToGeometry)
  860. {
  861. FitRaycast();
  862. FitPortal();
  863. }
  864. }
  865. if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(AAkAcousticPortal, DetectionRadius))
  866. {
  867. if (FitToGeometry)
  868. {
  869. if (!bUseSavedRaycastOrigin)
  870. {
  871. // Cache the actor position to get consistant results over multiple updates, since FitPortal() changes the actor location.
  872. SavedRaycastOrigin = GetActorLocation();
  873. bUseSavedRaycastOrigin = true;
  874. }
  875. FitRaycast();
  876. FitPortal();
  877. }
  878. }
  879. }
  880. }
  881. void AAkAcousticPortal::ClearBestFit()
  882. {
  883. BestFit[0] = FVector::ZeroVector;
  884. BestFit[1] = FVector::ZeroVector;
  885. BestFit[2] = FVector::ZeroVector;
  886. BestFit[3] = FVector::ZeroVector;
  887. BestFitValid = false;
  888. }
  889. #endif // WITH_EDITOR