AkAcousticPortal.cpp 35 KB

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