AkSpatialAudioVolume.cpp 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869
  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. AkSpatialAudioVolume.cpp:
  17. =============================================================================*/
  18. #include "AkSpatialAudioVolume.h"
  19. #include "AkSpatialAudioHelper.h"
  20. #include "AkAudioDevice.h"
  21. #include "AkLateReverbComponent.h"
  22. #include "AkRoomComponent.h"
  23. #include "AkSurfaceReflectorSetComponent.h"
  24. #include "AkAcousticPortal.h"
  25. #include "AkSettings.h"
  26. #include "WwiseUnrealDefines.h"
  27. #include "WwiseUnrealEngineHelper.h"
  28. #include "WwiseUnrealObjectHelper.h"
  29. #include "Components/BrushComponent.h"
  30. #include "Model.h"
  31. #include "Engine/BrushBuilder.h"
  32. // Geometric Tools
  33. #if WITH_EDITOR
  34. #include "AkAudioStyle.h"
  35. #include "Mathematics/Math.h"
  36. #include "Mathematics/UIntegerAP32.h"
  37. #include "Mathematics/BSRational.h"
  38. #include "Mathematics/MinimumVolumeBox3.h"
  39. #if UE_5_1_OR_LATER
  40. #include "Misc/TransactionObjectEvent.h"
  41. #endif
  42. #endif
  43. static const float kScaleEpsilon = 0.001;
  44. static const float kConvexHullEpsilon = 0.001;
  45. static const FName NAME_SAV_Fit = TEXT("AkSpatialAudioVolumeRaycast");
  46. #if WITH_EDITOR
  47. bool IntersectPlanes(FVector n0, float d0, FVector n1, float d1, FVector n2, float d2, FVector &p)
  48. {
  49. FVector u = FVector::CrossProduct(n1, n2);
  50. float denom = FVector::DotProduct(n0, u);
  51. if (std::abs(denom) < 0.1)
  52. return false; // Planes do not intersect in a point
  53. p = (d0*u + FVector::CrossProduct(n0, (d2 * n1) - (d1 * n2))) / denom;
  54. return true;
  55. }
  56. WwiseGTE::Vector3< float > ToGTEVector(FVector& In)
  57. {
  58. WwiseGTE::Vector3< float > Out;
  59. Out[0] = In.X;
  60. Out[1] = In.Y;
  61. Out[2] = In.Z;
  62. return Out;
  63. }
  64. FVector ToFVector(WwiseGTE::Vector3< float >& In)
  65. {
  66. return FVector(In[0], In[1], In[2]);
  67. }
  68. #endif
  69. /*------------------------------------------------------------------------------------
  70. AAkSpatialAudioVolume
  71. ------------------------------------------------------------------------------------*/
  72. AAkSpatialAudioVolume::AAkSpatialAudioVolume(const class FObjectInitializer& ObjectInitializer) :
  73. Super(ObjectInitializer)
  74. {
  75. // Property initialization
  76. UBrushComponent* BrushComp = GetBrushComponent();
  77. if (BrushComp)
  78. {
  79. BrushComp->SetGenerateOverlapEvents(false);
  80. BrushComp->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
  81. BrushComp->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
  82. BrushComp->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
  83. }
  84. static const FName SurfReflSetName = TEXT("SurfaceReflector");
  85. SurfaceReflectorSet = ObjectInitializer.CreateDefaultSubobject<UAkSurfaceReflectorSetComponent>(this, SurfReflSetName);
  86. SurfaceReflectorSet->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform);
  87. static const FName LateReverbame = TEXT("LateReverb");
  88. LateReverb = ObjectInitializer.CreateDefaultSubobject<UAkLateReverbComponent>(this, LateReverbame);
  89. LateReverb->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform);
  90. static const FName RoomName = TEXT("Room");
  91. Room = ObjectInitializer.CreateDefaultSubobject<UAkRoomComponent>(this, RoomName);
  92. Room->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform);
  93. bColored = true;
  94. BrushColor = FColor(109, 187, 255, 255);
  95. #if WITH_EDITOR
  96. CollisionChannel = EAkCollisionChannel::EAKCC_UseIntegrationSettingsDefault;
  97. PrimaryActorTick.bCanEverTick = true;
  98. PrimaryActorTick.bStartWithTickEnabled = true;
  99. #endif
  100. }
  101. #if WITH_EDITOR
  102. ECollisionChannel AAkSpatialAudioVolume::GetCollisionChannel()
  103. {
  104. return UAkSettings::ConvertFitToGeomCollisionChannel(CollisionChannel.GetValue());
  105. }
  106. void AAkSpatialAudioVolume::FitRaycast()
  107. {
  108. UWorld* World = GEngine->GetWorldFromContextObjectChecked(this);
  109. if (!World)
  110. return;
  111. TArray<FHitResult> hits;
  112. hits.Reserve(kNumRaycasts);
  113. const float kRayLength = 10000000.f;
  114. FCollisionQueryParams CollisionParams(NAME_SAV_Fit, true, this);
  115. CollisionParams.bReturnPhysicalMaterial = true;
  116. FVector RaycastOrigin = GetActorLocation();
  117. float Offset = 2.f / kNumRaycasts;
  118. float Increment = PI * (3.f - sqrtf(5.f));
  119. for (int i = 0; i < kNumRaycasts; ++i)
  120. {
  121. float y = ((i * Offset) - 1) + (Offset / 2);
  122. float r = sqrtf(1.f - powf(y, 2.f));
  123. float phi = ((i + 1) % kNumRaycasts) * Increment;
  124. float x = cosf(phi) * r;
  125. float z = sinf(phi) * r;
  126. FVector to = RaycastOrigin + FVector(x, y, z) * kRayLength;
  127. TArray< FHitResult > OutHits;
  128. OutHits.Empty();
  129. World->LineTraceMultiByObjectType(OutHits, RaycastOrigin, to, (int)GetCollisionChannel(), CollisionParams);
  130. for (auto& res : OutHits)
  131. {
  132. AActor* HitActor = WwiseUnrealHelper::GetActorFromHitResult(res);
  133. if (HitActor != nullptr)
  134. {
  135. UAkPortalComponent* PortalComponent = (UAkPortalComponent*)HitActor->FindComponentByClass(UAkPortalComponent::StaticClass());
  136. if (PortalComponent != nullptr)
  137. {
  138. // We hit a portal. The portals are a good reference point for the SAV, but we need to extend the ray to the center of the portal
  139. FVector PortalNorm = PortalComponent->GetComponentTransform().GetRotation().RotateVector(FVector(0.f, 1.f, 0.f));
  140. FVector PortalPos = PortalComponent->GetComponentLocation();
  141. float d = FVector::DotProduct(PortalPos, PortalNorm);
  142. FVector ab = to - res.ImpactPoint;
  143. float t = (d - FVector::DotProduct(PortalNorm, res.ImpactPoint)) / FVector::DotProduct(PortalNorm, ab);
  144. if (t >= 0.f && t < 1.0f)
  145. {
  146. FVector ImpactPointOnPoralPlane = res.ImpactPoint + t * ab;
  147. FHitResult ModifiedHitResult = res;
  148. ModifiedHitResult.ImpactPoint = ImpactPointOnPoralPlane;
  149. ModifiedHitResult.ImpactNormal = PortalNorm;
  150. if (FVector::DotProduct(PortalNorm, res.ImpactNormal) < 0.f)
  151. ModifiedHitResult.ImpactNormal = -PortalNorm;
  152. hits.Emplace(ModifiedHitResult);
  153. break;
  154. }
  155. }
  156. if (!res.bStartPenetrating &&
  157. HitActor->GetClass() == AAkSpatialAudioVolume::StaticClass())
  158. {
  159. hits.Emplace(res);
  160. break;
  161. }
  162. }
  163. if (res.IsValidBlockingHit() )
  164. {
  165. hits.Emplace(res);
  166. break;
  167. }
  168. }
  169. }
  170. auto SortPredicate = [](FHitResult& A, FHitResult& B){ return A.Distance < B.Distance; };
  171. Algo::Sort(hits, SortPredicate);
  172. FitPoints.Empty();
  173. FitPoints.Reserve(hits.Num());
  174. FitMaterials.Empty();
  175. FitMaterials.Reserve(hits.Num());
  176. FitNormals.Empty();
  177. FitNormals.Reserve(hits.Num());
  178. for (int i = 0; i < hits.Num(); ++i)
  179. {
  180. FitPoints.Emplace(hits[i].ImpactPoint);
  181. FitNormals.Emplace(hits[i].ImpactNormal);
  182. FitMaterials.Emplace(hits[i].PhysMaterial);
  183. }
  184. }
  185. void AAkSpatialAudioVolume::PostRebuildBrush()
  186. {
  187. UnregisterAllComponents(true);
  188. RegisterAllComponents();
  189. FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get();
  190. if (AkAudioDevice != nullptr)
  191. AkAudioDevice->PortalsNeedRoomUpdate(GetWorld());
  192. if (SurfaceReflectorSet != nullptr)
  193. {
  194. SurfaceReflectorSet->UpdatePolys();
  195. SurfaceReflectorSet->UpdateSurfaceReflectorSet();
  196. }
  197. ClearTextComponents();
  198. }
  199. void AAkSpatialAudioVolume::ClearTextComponents()
  200. {
  201. for (int i = 0; i < PreviewTextureNameComponents.Num(); ++i)
  202. {
  203. UTextRenderComponent* textComp = PreviewTextureNameComponents[i];
  204. if (textComp != nullptr)
  205. {
  206. textComp->DestroyComponent();
  207. }
  208. }
  209. PreviewTextureNameComponents.Empty();
  210. }
  211. void AAkSpatialAudioVolume::UpdatePreviewTextComponents(TArray<FVector> positions)
  212. {
  213. ClearTextComponents();
  214. int index = 0;
  215. UMaterialInterface* mat = Cast<UMaterialInterface>(FAkAudioStyle::GetAkForegroundTextMaterial());
  216. ensure(positions.Num() == PreviewPolys.Num());
  217. for (FAkSurfacePoly& Poly : PreviewPolys)
  218. {
  219. FString VizName = GetName() + TEXT("PolyPreviewText ") + FString::FromInt(index);
  220. if (Poly.EnableSurface)
  221. {
  222. int32 idx = PreviewTextureNameComponents.Add(NewObject<UTextRenderComponent>(GetOuter(), *VizName));
  223. UTextRenderComponent* textComp = PreviewTextureNameComponents[idx];
  224. if (textComp != nullptr)
  225. {
  226. if (mat != nullptr)
  227. textComp->SetTextMaterial(mat);
  228. textComp->RegisterComponentWithWorld(GetWorld());
  229. textComp->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepWorldTransform);
  230. textComp->bIsEditorOnly = true;
  231. textComp->bSelectable = false;
  232. textComp->bAlwaysRenderAsText = true;
  233. textComp->SetHorizontalAlignment(EHTA_Center);
  234. textComp->SetVerticalAlignment(EVRTA_TextCenter);
  235. bool displayOcclusion = true;
  236. if (SurfaceReflectorSet != nullptr)
  237. displayOcclusion = SurfaceReflectorSet->bEnableSurfaceReflectors;
  238. textComp->SetText(Poly.GetPolyText(displayOcclusion));
  239. textComp->SetWorldLocation(positions[index]);
  240. }
  241. }
  242. else
  243. {
  244. PreviewTextureNameComponents.Add(nullptr);
  245. }
  246. ++index;
  247. }
  248. }
  249. void AAkSpatialAudioVolume::UpdatePreviewPolys(TArray<TMap<TWeakObjectPtr<UPhysicalMaterial>, int>> materialVotes)
  250. {
  251. PreviewPolys.Empty();
  252. PreviewPolys.AddDefaulted(materialVotes.Num());
  253. for (int i = 0; i < materialVotes.Num(); ++i)
  254. {
  255. PreviewPolys[i].EnableSurface = false;
  256. TMap<TWeakObjectPtr<UPhysicalMaterial>, int>& votes = materialVotes[i];
  257. // Tally the votes
  258. if (votes.Num() > 0)
  259. {
  260. auto MaxVotes = *votes.begin();
  261. auto it = votes.begin();
  262. ++it;
  263. while (it != votes.end())
  264. {
  265. if (it->Value > MaxVotes.Value)
  266. MaxVotes = *it;
  267. ++it;
  268. }
  269. // Use the material with the max number of points.
  270. if (MaxVotes.Key.IsValid())
  271. {
  272. GetDefault<UAkSettings>()->GetAssociatedAcousticTexture(MaxVotes.Key.Get(), PreviewPolys[i].Texture);
  273. GetDefault<UAkSettings>()->GetAssociatedOcclusionValue(MaxVotes.Key.Get(), PreviewPolys[i].Occlusion);
  274. PreviewPolys[i].EnableSurface = true;
  275. }
  276. }
  277. }
  278. }
  279. void AddOrIncrementMaterialVote(TMap<TWeakObjectPtr<UPhysicalMaterial>, int>& votes, TWeakObjectPtr<UPhysicalMaterial>& material)
  280. {
  281. int* Count = votes.Find(material);
  282. if (Count == nullptr)
  283. {
  284. Count = &votes.Add(material);
  285. *Count = 0;
  286. }
  287. if (Count != nullptr)
  288. {
  289. *Count += 1;
  290. };
  291. }
  292. void AAkSpatialAudioVolume::FitBox(bool bPreviewOnly)
  293. {
  294. ClearTextComponents();
  295. LongestEdgeLength = 0.0f;
  296. if (FitPoints.Num() * FilterHitPoints == 0)
  297. {
  298. FitFailed = true;
  299. return;
  300. }
  301. static const float kExtent = 100.f;
  302. using MinimumVolumeBox3 = WwiseGTE::MinimumVolumeBox3<float, ::WwiseGTE::BSRational<::WwiseGTE::UIntegerAP32>>;
  303. using OBB = WwiseGTE::OrientedBox3 < float >;
  304. using Vector3 = WwiseGTE::Vector3< float >;
  305. PreviewOutline.Empty();
  306. const float kNormalAgreement = 0.866f; // ~30 degrees
  307. if (Shape == EAkFitToGeometryMode::OrientedBox ||
  308. Shape == EAkFitToGeometryMode::AlignedBox )
  309. {
  310. FTransform XF = GetActorTransform();
  311. if (Shape == EAkFitToGeometryMode::OrientedBox) //allow rotation.
  312. {
  313. TArray<Vector3> Points;
  314. Points.Reserve(FitPoints.Num());
  315. for (int i = 0; i < FitPoints.Num() && i < FitPoints.Num()*FilterHitPoints; ++i)
  316. {
  317. Points.Emplace(ToGTEVector(FitPoints[i]));
  318. }
  319. MinimumVolumeBox3 mvb(std::min(8U, std::thread::hardware_concurrency()-1), true);
  320. OBB obb = mvb(Points.Num(), &Points[0], kConvexHullEpsilon);
  321. FVector Location(obb.center[0], obb.center[1], obb.center[2]);
  322. FVector Front(obb.axis[1][0], obb.axis[1][1], obb.axis[1][2]);
  323. FVector Top(obb.axis[2][0], obb.axis[2][1], obb.axis[2][2]);
  324. FQuat Rotation = FRotationMatrix::MakeFromYZ(Front, Top).ToQuat();
  325. FVector Scale(obb.extent[0], obb.extent[1], obb.extent[2]);
  326. if (Scale.X > ::kScaleEpsilon &&
  327. Scale.Y > ::kScaleEpsilon &&
  328. Scale.Z > ::kScaleEpsilon)
  329. {
  330. LongestEdgeLength = Scale.GetAbsMax();
  331. // Compensate for the standard AAkSpatialAudioVolume, based on a cube brush with verts at [+/-]100 X,Y,Z.
  332. Scale /= kExtent;
  333. XF.SetLocation(Location);
  334. XF.SetRotation(Rotation);
  335. XF.SetScale3D(Scale);
  336. }
  337. TArray<FVector> textPositions = TArray<FVector>();
  338. textPositions.AddDefaulted(6); // Box face planes
  339. // The material votes for each plane.
  340. TArray<TMap<TWeakObjectPtr<UPhysicalMaterial>, int>> MaterialVotes;
  341. MaterialVotes.AddDefaulted(6);
  342. for (int boxAxis = 0; boxAxis < 3; ++boxAxis)
  343. {
  344. int faceIndex = boxAxis * 2;
  345. int oppositeFaceIndex = faceIndex + 1;
  346. FVector axis(obb.axis[boxAxis][0], obb.axis[boxAxis][1], obb.axis[boxAxis][2]);
  347. FVector faceCenter = Location + axis * obb.extent[boxAxis];
  348. FVector oppositeFaceCenter = Location - axis * obb.extent[boxAxis];
  349. textPositions[faceIndex] = faceCenter;
  350. textPositions[oppositeFaceIndex] = oppositeFaceCenter;
  351. for (int i = 0; i < FitPoints.Num() && i < FitPoints.Num() * FilterHitPoints; ++i)
  352. {
  353. // The sign on the axis is flipped in the comparisons because the normals face in towards the cuboid...
  354. if (FVector::DotProduct(FitNormals[i], -axis) >= kNormalAgreement && FMath::IsNearlyZero(FVector::DotProduct(FitPoints[i] - faceCenter, axis), 0.01f))
  355. {
  356. TMap<TWeakObjectPtr<UPhysicalMaterial>, int>& votes = MaterialVotes[faceIndex];
  357. AddOrIncrementMaterialVote(votes, FitMaterials[i]);
  358. }
  359. if (FVector::DotProduct(FitNormals[i], axis) >= kNormalAgreement && FMath::IsNearlyZero(FVector::DotProduct(FitPoints[i] - oppositeFaceCenter, -axis), 0.01f))
  360. {
  361. TMap<TWeakObjectPtr<UPhysicalMaterial>, int>& votes = MaterialVotes[oppositeFaceIndex];
  362. AddOrIncrementMaterialVote(votes, FitMaterials[i]);
  363. }
  364. }
  365. }
  366. if (bPreviewOnly)
  367. {
  368. UpdatePreviewPolys(MaterialVotes);
  369. UpdatePreviewTextComponents(textPositions);
  370. }
  371. }
  372. else if (Shape == EAkFitToGeometryMode::AlignedBox)
  373. {
  374. USceneComponent* RC = GetRootComponent();
  375. if (RC)
  376. {
  377. FRotator Rotation = RC->GetComponentRotation();
  378. FVector Min(FLT_MAX, FLT_MAX, FLT_MAX);
  379. FVector Max(-FLT_MAX, -FLT_MAX, -FLT_MAX);
  380. for (int i = 0; i < FitPoints.Num() && i < FitPoints.Num()*FilterHitPoints; ++i)
  381. {
  382. FVector PtWorld(FitPoints[i][0], FitPoints[i][1], FitPoints[i][2]);
  383. FVector PtLocal = Rotation.UnrotateVector(PtWorld);
  384. Min = Min.ComponentMin(PtLocal);
  385. Max = Max.ComponentMax(PtLocal);
  386. }
  387. FVector Scale = Max - Min;
  388. if (Scale.X > ::kScaleEpsilon &&
  389. Scale.Y > ::kScaleEpsilon &&
  390. Scale.Z > ::kScaleEpsilon)
  391. {
  392. LongestEdgeLength = Scale.GetAbsMax();
  393. FVector Location = Rotation.RotateVector((Min + Max) / 2.f);
  394. XF.SetLocation(Location);
  395. XF.SetScale3D(Scale / (2.f*kExtent));
  396. }
  397. FVector centre(XF.GetLocation());
  398. FVector extent(Scale / 2.0f);
  399. TArray<FVector> textPositions = // Box face planes
  400. {
  401. centre + FVector(extent.X, 0.0f, 0.0f), centre - FVector(extent.X, 0.0f, 0.0f),
  402. centre + FVector(0.0f, extent.Y, 0.0f), centre - FVector(0.0f, extent.Y, 0.0f),
  403. centre + FVector(0.0f, 0.0f, extent.Z), centre - FVector(0.0f, 0.0f, extent.Z)
  404. };
  405. TArray<FVector> faceNormals =
  406. {
  407. FVector(1, 0, 0), FVector(-1, 0, 0), FVector(0, 1, 0), FVector(0, -1, 0), FVector(0, 0, 1), FVector(0, 0, -1)
  408. };
  409. // The material votes for each plane.
  410. TArray<TMap<TWeakObjectPtr<UPhysicalMaterial>, int>> materialVotes;
  411. materialVotes.AddDefaulted(6);
  412. for (int axisIndex = 0; axisIndex < textPositions.Num(); ++axisIndex)
  413. {
  414. for (int i = 0; i < FitPoints.Num() && i < FitPoints.Num() * FilterHitPoints; ++i)
  415. {
  416. if (FVector::DotProduct(-FitNormals[i], faceNormals[axisIndex]) >= kNormalAgreement && FMath::IsNearlyZero(FVector::DotProduct(FitPoints[i] - textPositions[axisIndex], faceNormals[axisIndex]), 0.01f))
  417. {
  418. TMap<TWeakObjectPtr<UPhysicalMaterial>, int>& votes = materialVotes[axisIndex];
  419. AddOrIncrementMaterialVote(votes, FitMaterials[i]);
  420. }
  421. }
  422. }
  423. if (bPreviewOnly)
  424. {
  425. UpdatePreviewPolys(materialVotes);
  426. UpdatePreviewTextComponents(textPositions);
  427. }
  428. }
  429. }
  430. if (!bPreviewOnly && BrushBuilder)
  431. {
  432. BrushBuilder->BeginBrush(true, this->GetFName());
  433. const FTransform& ActorTransform = GetActorTransform();
  434. for (int32 i = -1; i < 2; i += 2)
  435. for (int32 j = -1; j < 2; j += 2)
  436. for (int32 k = -1; k < 2; k += 2)
  437. BrushBuilder->Vertexv(ActorTransform.InverseTransformPosition( XF.TransformPosition(FVector(i*kExtent, j*kExtent, k*kExtent)) ));
  438. BrushBuilder->Poly4i(+1, 0, 1, 3, 2);
  439. BrushBuilder->Poly4i(+1, 2, 3, 7, 6);
  440. BrushBuilder->Poly4i(+1, 6, 7, 5, 4);
  441. BrushBuilder->Poly4i(+1, 4, 5, 1, 0);
  442. BrushBuilder->Poly4i(+1, 3, 1, 5, 7);
  443. BrushBuilder->Poly4i(+1, 0, 2, 6, 4);
  444. BrushBuilder->EndBrush(GetWorld(), this);
  445. SetNeedRebuild(GetLevel());
  446. GEditor->RebuildAlteredBSP();
  447. PostRebuildBrush();
  448. }
  449. PreviewOutline.Emplace(TPair<FVector, FVector>(XF.TransformPosition(FVector(+kExtent, +kExtent, -kExtent)), XF.TransformPosition(FVector(+kExtent, +kExtent, +kExtent))));
  450. PreviewOutline.Emplace(TPair<FVector, FVector>(XF.TransformPosition(FVector(+kExtent, -kExtent, -kExtent)), XF.TransformPosition(FVector(+kExtent, -kExtent, +kExtent))));
  451. PreviewOutline.Emplace(TPair<FVector, FVector>(XF.TransformPosition(FVector(-kExtent, +kExtent, -kExtent)), XF.TransformPosition(FVector(-kExtent, +kExtent, +kExtent))));
  452. PreviewOutline.Emplace(TPair<FVector, FVector>(XF.TransformPosition(FVector(-kExtent, -kExtent, -kExtent)), XF.TransformPosition(FVector(-kExtent, -kExtent, +kExtent))));
  453. PreviewOutline.Emplace(TPair<FVector, FVector>(XF.TransformPosition(FVector(-kExtent, -kExtent, -kExtent)), XF.TransformPosition(FVector(-kExtent, +kExtent, -kExtent))));
  454. PreviewOutline.Emplace(TPair<FVector, FVector>(XF.TransformPosition(FVector(+kExtent, -kExtent, -kExtent)), XF.TransformPosition(FVector(+kExtent, +kExtent, -kExtent))));
  455. PreviewOutline.Emplace(TPair<FVector, FVector>(XF.TransformPosition(FVector(-kExtent, -kExtent, -kExtent)), XF.TransformPosition(FVector(+kExtent, -kExtent, -kExtent))));
  456. PreviewOutline.Emplace(TPair<FVector, FVector>(XF.TransformPosition(FVector(-kExtent, +kExtent, -kExtent)), XF.TransformPosition(FVector(+kExtent, +kExtent, -kExtent))));
  457. PreviewOutline.Emplace(TPair<FVector, FVector>(XF.TransformPosition(FVector(-kExtent, -kExtent, +kExtent)), XF.TransformPosition(FVector(-kExtent, +kExtent, +kExtent))));
  458. PreviewOutline.Emplace(TPair<FVector, FVector>(XF.TransformPosition(FVector(+kExtent, -kExtent, +kExtent)), XF.TransformPosition(FVector(+kExtent, +kExtent, +kExtent))));
  459. PreviewOutline.Emplace(TPair<FVector, FVector>(XF.TransformPosition(FVector(-kExtent, -kExtent, +kExtent)), XF.TransformPosition(FVector(+kExtent, -kExtent, +kExtent))));
  460. PreviewOutline.Emplace(TPair<FVector, FVector>(XF.TransformPosition(FVector(-kExtent, +kExtent, +kExtent)), XF.TransformPosition(FVector(+kExtent, +kExtent, +kExtent))));
  461. }
  462. else if (Shape == EAkFitToGeometryMode::ConvexPolyhedron)
  463. {
  464. static const int kMaxAllowedPointsBehindPlane = 1; // Allows for some leniency - we cant expect geometry to be completely convex.
  465. static const float kDotEpsilon = 0.1f; // To determine if points are infront/behind a given plane.
  466. static const float kDotThreshold = 0.866f; //~ 30 degrees, enough for a polygonal cross section with 12 sides. Used for comparing normals.
  467. using ConvexHull3 = ::WwiseGTE::ConvexHull3<float, ::WwiseGTE::BSRational<::WwiseGTE::UIntegerAP32>>;
  468. using ETManifoldMesh = ::WwiseGTE::ETManifoldMesh;
  469. FVector Origin = GetActorLocation();
  470. TArray<Vector3> Points;
  471. Points.Reserve(FitPoints.Num());
  472. TArray<FVector> Normals;
  473. // The material votes for each plane.
  474. TArray<TMap<TWeakObjectPtr<UPhysicalMaterial>, int>> MaterialVotes;
  475. TArray<float> Ds;
  476. for (int i = 0; i < FitPoints.Num()*FilterHitPoints; ++i)
  477. {
  478. // If the room is convex, every other point should be in front of the plane defined by the hit point and normal.
  479. int PointsBehindPlane = 0;
  480. for (int j = 0; j < FitPoints.Num()*FilterHitPoints; ++j)
  481. {
  482. if (i != j)
  483. {
  484. FVector ToPt = FitPoints[j] - FitPoints[i];
  485. ToPt.Normalize();
  486. float Proj = FVector::DotProduct(ToPt, FitNormals[i]);
  487. if (Proj < -kDotEpsilon)
  488. PointsBehindPlane++;
  489. }
  490. }
  491. // But in practice we will allow 1 point to be behind.
  492. if (PointsBehindPlane <= kMaxAllowedPointsBehindPlane)
  493. {
  494. // Calculate a representative plane for this point to be used in the convex hull algorithm.
  495. float d = FVector::DotProduct(Origin - FitPoints[i], FitNormals[i]);
  496. FVector HullPoint = Origin - FitNormals[i] * d;
  497. FVector& Normal = FitNormals[i];
  498. TWeakObjectPtr<UPhysicalMaterial>& Material = FitMaterials[i];
  499. bool Found = false;
  500. for (int n = 0; n < Normals.Num(); ++n)
  501. {
  502. // Check to see if a plane with the same normal has already been found.
  503. if (FVector::DotProduct(Normals[n], Normal) > kDotThreshold)
  504. {
  505. // If so, take the one with the largest d value.
  506. Found = true;
  507. if (Ds[n] < d)
  508. {
  509. Normals[n] = Normal;
  510. Ds[n] = d;
  511. Points[n] = ToGTEVector(HullPoint);
  512. }
  513. TMap<TWeakObjectPtr<UPhysicalMaterial>, int>& votes = MaterialVotes[n];
  514. AddOrIncrementMaterialVote(votes, Material);
  515. }
  516. }
  517. if (!Found)
  518. {
  519. MaterialVotes.Add(TMap<TWeakObjectPtr<UPhysicalMaterial>, int>());
  520. TMap<TWeakObjectPtr<UPhysicalMaterial>, int>& votes = MaterialVotes.Last();
  521. AddOrIncrementMaterialVote(votes, Material);
  522. Normals.Add(Normal);
  523. Ds.Add(d);
  524. Points.Emplace(ToGTEVector(HullPoint));
  525. }
  526. }
  527. }
  528. if (Points.Num() < 4)
  529. {
  530. FitFailed = true;
  531. return;
  532. }
  533. // Build a convex hull with the planes found from the raycasts.
  534. ConvexHull3 ConvexHull;
  535. if (ConvexHull(Points.Num(), &Points[0], kConvexHullEpsilon))
  536. {
  537. ETManifoldMesh RoughMesh = ConvexHull.GetHullMesh();
  538. //At this point the polyhedron mesh is missing 'corners'. Iterate through the triangles, checking the normals of the vertices.
  539. TArray<Vector3> MeshPoints;
  540. MeshPoints.Reserve(FitPoints.Num());
  541. ETManifoldMesh::TMap RoughTriangles = RoughMesh.GetTriangles();
  542. for (auto& Triangle : RoughTriangles)
  543. {
  544. auto& Indexes = Triangle.second->V;
  545. FVector& n0 = Normals[Indexes[0]];
  546. FVector& n1 = Normals[Indexes[1]];
  547. FVector& n2 = Normals[Indexes[2]];
  548. FVector p0 = ToFVector(Points[Indexes[0]]);
  549. FVector p1 = ToFVector(Points[Indexes[1]]);
  550. FVector p2 = ToFVector(Points[Indexes[2]]);
  551. float d0 = FVector::DotProduct(p0, n0);
  552. float d1 = FVector::DotProduct(p1, n1);
  553. float d2 = FVector::DotProduct(p2, n2);
  554. FVector CornerPoint;
  555. if (IntersectPlanes(n0, d0, n1, d1, n2, d2, CornerPoint))
  556. {
  557. // Punch out the corner
  558. MeshPoints.Emplace(ToGTEVector(CornerPoint));
  559. }
  560. else
  561. {
  562. FitFailed = true;
  563. return;
  564. }
  565. }
  566. // Now generate a convex hull with the new corner points.
  567. ConvexHull3 ConvexPolyhedron;
  568. if (ConvexPolyhedron(MeshPoints.Num(), &MeshPoints[0], kConvexHullEpsilon))
  569. {
  570. ETManifoldMesh Mesh = ConvexPolyhedron.GetHullMesh();
  571. // Build a new brush with the polyhedron mesh.
  572. if (!bPreviewOnly &&
  573. BrushBuilder)
  574. {
  575. BrushBuilder->BeginBrush(true, this->GetFName());
  576. FVector Location = GetActorLocation();
  577. for (int p = 0; p < ConvexPolyhedron.GetNumPoints(); ++p)
  578. {
  579. const Vector3& Vert = ConvexPolyhedron.GetPoints()[p];
  580. BrushBuilder->Vertex3f( Vert[0] - Location.X,
  581. Vert[1] - Location.Y,
  582. Vert[2] - Location.Z);
  583. }
  584. ETManifoldMesh::TMap Triangles = Mesh.GetTriangles();
  585. for (auto& Triangle : Triangles)
  586. {
  587. auto& Indexes = Triangle.second->V;
  588. BrushBuilder->Poly3i(+1, Indexes[0], Indexes[1], Indexes[2]);
  589. }
  590. BrushBuilder->EndBrush(GetWorld(), this);
  591. auto* RC = GetRootComponent();
  592. if (RC)
  593. {
  594. RC->SetWorldRotation(FQuat::Identity);
  595. RC->SetWorldScale3D(FVector::OneVector);
  596. }
  597. SetNeedRebuild(GetLevel());
  598. GEditor->RebuildAlteredBSP();
  599. PostRebuildBrush();
  600. }
  601. // Add the edges to the preview outline - skipping the edges that are 'flat'.
  602. ETManifoldMesh::EMap Edges = Mesh.GetEdges();
  603. for (auto& E : Edges)
  604. {
  605. auto& Edge = *E.second;
  606. auto T0 = Edge.T[0].lock();
  607. auto T1 = Edge.T[1].lock();
  608. if (T0 != nullptr && T1 != nullptr)
  609. {
  610. FVector N0 = FVector::CrossProduct(ToFVector(MeshPoints[T0->V[1]]) - ToFVector(MeshPoints[T0->V[0]]), ToFVector(MeshPoints[T0->V[2]]) - ToFVector(MeshPoints[T0->V[0]]));
  611. FVector N1 = FVector::CrossProduct(ToFVector(MeshPoints[T1->V[1]]) - ToFVector(MeshPoints[T1->V[0]]), ToFVector(MeshPoints[T1->V[2]]) - ToFVector(MeshPoints[T1->V[0]]));
  612. N0.Normalize();
  613. N1.Normalize();
  614. if (FVector::DotProduct(N0, N1) < kDotThreshold)
  615. {
  616. PreviewOutline.Add(TPair<FVector, FVector>(ToFVector(MeshPoints[Edge.V[0]]), ToFVector(MeshPoints[Edge.V[1]])));
  617. float edgeLength = (PreviewOutline.Last().Value - PreviewOutline.Last().Key).Size();
  618. if (edgeLength > LongestEdgeLength)
  619. LongestEdgeLength = edgeLength;
  620. }
  621. }
  622. }
  623. }
  624. }
  625. if (bPreviewOnly)
  626. {
  627. UpdatePreviewPolys(MaterialVotes);
  628. TArray<FVector> textPositions = TArray<FVector>();
  629. textPositions.AddDefaulted(Points.Num());
  630. for (int posIndex = 0; posIndex < Points.Num(); ++posIndex)
  631. {
  632. textPositions[posIndex] = ToFVector(Points[posIndex]);
  633. }
  634. UpdatePreviewTextComponents(textPositions);
  635. }
  636. }
  637. else
  638. {
  639. check(false);
  640. }
  641. // Map physics materials to surfaces.
  642. if ((!bPreviewOnly) && SurfaceReflectorSet)
  643. {
  644. SurfaceReflectorSet->AssignAcousticTexturesFromSamples(FitPoints, FitNormals, FitMaterials, std::min(FitPoints.Num(), (int32)(FitPoints.Num()*FilterHitPoints)));
  645. }
  646. FitFailed = false;
  647. }
  648. bool AAkSpatialAudioVolume::ShouldTickIfViewportsOnly() const
  649. {
  650. return bBrushNeedsRebuild || ((GetBounds() != PreviousBounds) && PreviousBounds.SphereRadius != 0.0f);
  651. }
  652. void AAkSpatialAudioVolume::Tick(float DeltaSeconds)
  653. {
  654. if (ShouldTickIfViewportsOnly())
  655. {
  656. SetNeedRebuild(GetLevel());
  657. GEditor->RebuildAlteredBSP();
  658. PostRebuildBrush();
  659. PreviousBounds = GetBounds();
  660. bBrushNeedsRebuild = false;
  661. }
  662. }
  663. void AAkSpatialAudioVolume::PostTransacted(const FTransactionObjectEvent& TransactionEvent)
  664. {
  665. Super::PostTransacted(TransactionEvent);
  666. const TArray<FName>& ChangedProperties = TransactionEvent.GetChangedProperties();
  667. if (TransactionEvent.GetEventType() == ETransactionObjectEventType::UndoRedo && ChangedProperties.Contains(FName("FitToGeometry")))
  668. {
  669. bBrushNeedsRebuild = true;
  670. }
  671. }
  672. void AAkSpatialAudioVolume::PostEditMove(bool bFinished)
  673. {
  674. Super::PostEditMove(bFinished);
  675. IsDragging = !bFinished;
  676. if (FitToGeometry)
  677. {
  678. FitRaycast();
  679. if (bFinished && Shape == EAkFitToGeometryMode::AlignedBox)
  680. {
  681. USceneComponent* RC = GetRootComponent();
  682. if (RC)
  683. SavedRotation = RC->GetComponentRotation();
  684. }
  685. FitBox(!bFinished);
  686. }
  687. }
  688. void AAkSpatialAudioVolume::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
  689. {
  690. Super::PostEditChangeProperty(PropertyChangedEvent);
  691. IsDragging = PropertyChangedEvent.ChangeType == EPropertyChangeType::Interactive;
  692. if (PropertyChangedEvent.Property)
  693. {
  694. if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(AAkSpatialAudioVolume, FitToGeometry))
  695. {
  696. if (FitToGeometry)
  697. {
  698. FitRaycast();
  699. FitBox();
  700. }
  701. else
  702. {
  703. FitPoints.Empty();
  704. }
  705. }
  706. if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(AAkSpatialAudioVolume, FilterHitPoints))
  707. {
  708. if (FitToGeometry) // only fit box continuously on value set for performance reasons.
  709. {
  710. FitBox(PropertyChangedEvent.ChangeType != EPropertyChangeType::ValueSet);
  711. }
  712. }
  713. if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(AAkSpatialAudioVolume, Shape))
  714. {
  715. USceneComponent* RC = GetRootComponent();
  716. if (RC)
  717. {
  718. if (Shape != EAkFitToGeometryMode::AlignedBox)
  719. {
  720. // We just disabled 'ConstrainRotation'. Save the old rotation.
  721. SavedRotation = RC->GetComponentRotation();
  722. }
  723. else
  724. {
  725. // We just enabled 'ConstrainRotation'. Restore the old rotation.
  726. RC->SetWorldRotation(SavedRotation);
  727. }
  728. }
  729. FitBox();
  730. }
  731. if (PropertyChangedEvent.Property->GetFName() == "ActorLabel")
  732. {
  733. if (Room != nullptr)
  734. {
  735. Room->OnParentNameChanged();
  736. }
  737. }
  738. }
  739. }
  740. #endif // WITH_EDITOR