1027 lines
28 KiB
C++
1027 lines
28 KiB
C++
/*******************************************************************************
|
|
The content of this file includes portions of the proprietary AUDIOKINETIC Wwise
|
|
Technology released in source code form as part of the game integration package.
|
|
The content of this file may not be used without valid licenses to the
|
|
AUDIOKINETIC Wwise Technology.
|
|
Note that the use of the game engine is subject to the Unreal(R) Engine End User
|
|
License Agreement at https://www.unrealengine.com/en-US/eula/unreal
|
|
|
|
License Usage
|
|
|
|
Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use
|
|
this file in accordance with the end user license agreement provided with the
|
|
software or, alternatively, in accordance with the terms contained
|
|
in a written agreement between you and Audiokinetic Inc.
|
|
Copyright (c) 2023 Audiokinetic Inc.
|
|
*******************************************************************************/
|
|
|
|
/*=============================================================================
|
|
AAkAcousticPortal.cpp:
|
|
=============================================================================*/
|
|
|
|
#include "AkAcousticPortal.h"
|
|
#include "AkAudioDevice.h"
|
|
#include "AkComponentHelpers.h"
|
|
#include "AkSpatialAudioHelper.h"
|
|
#include "AkSpatialAudioDrawUtils.h"
|
|
#include "Components/BrushComponent.h"
|
|
#include "Model.h"
|
|
#include "EngineUtils.h"
|
|
#include "AkRoomComponent.h"
|
|
#include "AkComponent.h"
|
|
#include "AkCustomVersion.h"
|
|
#include "Kismet/KismetMathLibrary.h"
|
|
#include "AkSpatialAudioVolume.h"
|
|
|
|
// A standard AAkAcousticPortal is based on a cube brush with verts at [+/-]100 X,Y,Z.
|
|
static const float kDefaultBrushExtents = 100.f;
|
|
|
|
// min portal size, in cm. For raycasts
|
|
static const float kMinPortalSize = 10.0f;
|
|
|
|
#if WITH_EDITOR
|
|
#include "AkDrawPortalComponent.h"
|
|
#include "AkAudioStyle.h"
|
|
#include "LevelEditorViewport.h"
|
|
#endif
|
|
|
|
UAkPortalComponent::UAkPortalComponent(const class FObjectInitializer& ObjectInitializer) :
|
|
Super(ObjectInitializer)
|
|
{
|
|
ObstructionRefreshInterval = 0.f;
|
|
|
|
PortalState = InitialState;
|
|
bUseAttachParentBound = true;
|
|
|
|
FrontRoom = nullptr;
|
|
BackRoom = nullptr;
|
|
|
|
PrimaryComponentTick.bCanEverTick = true;
|
|
PrimaryComponentTick.bStartWithTickEnabled = true;
|
|
bTickInEditor = true;
|
|
#if WITH_EDITOR
|
|
bWantsOnUpdateTransform = true;
|
|
bWantsInitializeComponent = true;
|
|
#else
|
|
bWantsOnUpdateTransform = bDynamic;
|
|
#endif
|
|
|
|
#if WITH_EDITOR
|
|
if (AkSpatialAudioHelper::GetObjectReplacedEvent())
|
|
{
|
|
AkSpatialAudioHelper::GetObjectReplacedEvent()->AddUObject(this, &UAkPortalComponent::HandleObjectsReplaced);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void UAkPortalComponent::OnRegister()
|
|
{
|
|
Super::OnRegister();
|
|
SetRelativeTransform(FTransform::Identity);
|
|
InitializeParent();
|
|
UpdateConnectedRooms();
|
|
|
|
UWorld* world = GetWorld();
|
|
if (world != nullptr)
|
|
SetSpatialAudioPortal();
|
|
|
|
#if WITH_EDITOR
|
|
if (GetDefault<UAkSettings>()->VisualizeRoomsAndPortals)
|
|
{
|
|
InitializeDrawComponent();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void UAkPortalComponent::OnUnregister()
|
|
{
|
|
Super::OnUnregister();
|
|
#if WITH_EDITOR
|
|
if (!HasAnyFlags(RF_Transient))
|
|
{
|
|
DestroyTextVisualizers();
|
|
}
|
|
#endif
|
|
FAkAudioDevice * Dev = FAkAudioDevice::Get();
|
|
if (Dev != nullptr)
|
|
{
|
|
Dev->RemoveSpatialAudioPortal(this);
|
|
}
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
void UAkPortalComponent::BeginDestroy()
|
|
{
|
|
Super::BeginDestroy();
|
|
if (AkSpatialAudioHelper::GetObjectReplacedEvent())
|
|
{
|
|
AkSpatialAudioHelper::GetObjectReplacedEvent()->RemoveAll(this);
|
|
}
|
|
}
|
|
|
|
void UAkPortalComponent::HandleObjectsReplaced(const TMap<UObject*, UObject*>& ReplacementMap)
|
|
{
|
|
if (ReplacementMap.Contains(Parent))
|
|
{
|
|
InitializeParent();
|
|
}
|
|
if (ReplacementMap.Contains(FrontRoom) || ReplacementMap.Contains(BackRoom))
|
|
{
|
|
UpdateConnectedRooms();
|
|
}
|
|
}
|
|
|
|
void UAkPortalComponent::InitializeComponent()
|
|
{
|
|
Super::InitializeComponent();
|
|
RegisterVisEnabledCallback();
|
|
}
|
|
|
|
void UAkPortalComponent::OnComponentCreated()
|
|
{
|
|
Super::OnComponentCreated();
|
|
RegisterVisEnabledCallback();
|
|
}
|
|
|
|
void UAkPortalComponent::PostLoad()
|
|
{
|
|
Super::PostLoad();
|
|
RegisterVisEnabledCallback();
|
|
}
|
|
|
|
void UAkPortalComponent::OnComponentDestroyed(bool bDestroyingHierarchy)
|
|
{
|
|
UAkSettings* AkSettings = GetMutableDefault<UAkSettings>();
|
|
AkSettings->OnShowRoomsPortalsChanged.Remove(ShowPortalsChangedHandle);
|
|
ShowPortalsChangedHandle.Reset();
|
|
DestroyDrawComponent();
|
|
}
|
|
#endif // WITH_EDITOR
|
|
|
|
bool UAkPortalComponent::MoveComponentImpl(
|
|
const FVector & Delta,
|
|
const FQuat & NewRotation,
|
|
bool bSweep,
|
|
FHitResult * Hit,
|
|
EMoveComponentFlags MoveFlags,
|
|
ETeleportType Teleport)
|
|
{
|
|
if (AkComponentHelpers::DoesMovementRecenterChild(this, Parent, Delta))
|
|
Super::MoveComponentImpl(Delta, NewRotation, bSweep, Hit, MoveFlags, Teleport);
|
|
|
|
return false;
|
|
}
|
|
|
|
void UAkPortalComponent::OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport)
|
|
{
|
|
PortalNeedUpdated = true;
|
|
|
|
#if WITH_EDITOR
|
|
UpdateTextLocRotVis();
|
|
#endif
|
|
}
|
|
|
|
|
|
#if WITH_EDITOR
|
|
void UAkPortalComponent::RegisterVisEnabledCallback()
|
|
{
|
|
if (!ShowPortalsChangedHandle.IsValid())
|
|
{
|
|
UAkSettings* AkSettings = GetMutableDefault<UAkSettings>();
|
|
ShowPortalsChangedHandle = AkSettings->OnShowRoomsPortalsChanged.AddLambda([this, AkSettings]()
|
|
{
|
|
if (AkSettings->VisualizeRoomsAndPortals)
|
|
{
|
|
InitializeDrawComponent();
|
|
}
|
|
else
|
|
{
|
|
DestroyDrawComponent();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
void UAkPortalComponent::InitializeDrawComponent()
|
|
{
|
|
if (AActor* Owner = GetOwner())
|
|
{
|
|
if (DrawPortalComponent == nullptr)
|
|
{
|
|
DrawPortalComponent = NewObject<UDrawPortalComponent>(Owner, NAME_None, RF_Transactional | RF_TextExportTransient);
|
|
DrawPortalComponent->SetupAttachment(this);
|
|
DrawPortalComponent->SetIsVisualizationComponent(true);
|
|
DrawPortalComponent->CreationMethod = CreationMethod;
|
|
DrawPortalComponent->RegisterComponentWithWorld(GetWorld());
|
|
DrawPortalComponent->MarkRenderStateDirty();
|
|
}
|
|
}
|
|
}
|
|
|
|
void UAkPortalComponent::DestroyDrawComponent()
|
|
{
|
|
if (DrawPortalComponent != nullptr)
|
|
{
|
|
DrawPortalComponent->DestroyComponent();
|
|
DrawPortalComponent = nullptr;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void UAkPortalComponent::InitializeParent()
|
|
{
|
|
USceneComponent* SceneParent = GetAttachParent();
|
|
if (SceneParent != nullptr)
|
|
{
|
|
Parent = Cast<UPrimitiveComponent>(SceneParent);
|
|
if (!Parent)
|
|
{
|
|
AkComponentHelpers::LogAttachmentError(this, SceneParent, "UPrimitiveComponent");
|
|
}
|
|
#if WITH_EDITOR
|
|
DestroyTextVisualizers();
|
|
InitTextVisualizers();
|
|
UpdateRoomNames();
|
|
UpdateTextLocRotVis();
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void UAkPortalComponent::SetSpatialAudioPortal()
|
|
{
|
|
FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get();
|
|
if (AkAudioDevice != nullptr)
|
|
{
|
|
AkAudioDevice->SetSpatialAudioPortal(this);
|
|
PortalNeedUpdated = false;
|
|
}
|
|
}
|
|
|
|
void UAkPortalComponent::OpenPortal()
|
|
{
|
|
if (PortalState == AkAcousticPortalState::Closed)
|
|
{
|
|
PortalState = AkAcousticPortalState::Open;
|
|
SetSpatialAudioPortal();
|
|
}
|
|
}
|
|
|
|
void UAkPortalComponent::ClosePortal()
|
|
{
|
|
if (PortalState == AkAcousticPortalState::Open)
|
|
{
|
|
PortalState = AkAcousticPortalState::Closed;
|
|
SetSpatialAudioPortal();
|
|
}
|
|
}
|
|
|
|
AkAcousticPortalState UAkPortalComponent::GetCurrentState() const
|
|
{
|
|
return PortalState;
|
|
}
|
|
|
|
void UAkPortalComponent::BeginPlay()
|
|
{
|
|
Super::BeginPlay();
|
|
// If we're PIE, or somehow otherwise in a game world in editor, simulate the bDynamic behaviour.
|
|
#if WITH_EDITOR
|
|
UWorld* world = GetWorld();
|
|
if (world != nullptr && (world->WorldType == EWorldType::Type::Game || world->WorldType == EWorldType::Type::PIE))
|
|
bWantsOnUpdateTransform = bDynamic;
|
|
#endif
|
|
UpdateConnectedRooms();
|
|
ResetPortalState();
|
|
FAkAudioDevice * Dev = FAkAudioDevice::Get();
|
|
if (Dev != nullptr)
|
|
{
|
|
ObstructionService.Init(this, ObstructionRefreshInterval);
|
|
}
|
|
}
|
|
|
|
void UAkPortalComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction * ThisTickFunction)
|
|
{
|
|
if (PortalNeedUpdated)
|
|
{
|
|
UpdateConnectedRooms();
|
|
SetSpatialAudioPortal();
|
|
}
|
|
|
|
if (GetCurrentState() == AkAcousticPortalState::Open)
|
|
{
|
|
FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get();
|
|
if (AkAudioDevice)
|
|
{
|
|
UAkComponent* Listener = AkAudioDevice->GetSpatialAudioListener();
|
|
if (Listener != nullptr)
|
|
{
|
|
AkRoomID listenerRoom = Listener->GetSpatialAudioRoom();
|
|
UAkComponentSet set;
|
|
set.Add(Listener);
|
|
ObstructionService.Tick(set, GetOwner()->GetActorLocation(), GetOwner(), listenerRoom, ObstructionCollisionChannel, DeltaTime, ObstructionRefreshInterval);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
UWorld* World = GetWorld();
|
|
if (World && (World->WorldType == EWorldType::Editor || World->WorldType == EWorldType::PIE))
|
|
{
|
|
// Only show the text renderer for selected actors.
|
|
if (GetOwner()->IsSelected() && !bWasSelected)
|
|
{
|
|
bWasSelected = true;
|
|
UpdateTextVisibility();
|
|
}
|
|
if (!GetOwner()->IsSelected() && bWasSelected)
|
|
{
|
|
bWasSelected = false;
|
|
UpdateTextVisibility();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void UAkPortalComponent::ResetPortalState()
|
|
{
|
|
PortalState = InitialState;
|
|
SetSpatialAudioPortal();
|
|
}
|
|
|
|
bool UAkPortalComponent::UpdateConnectedRooms()
|
|
{
|
|
/* Keep note of the rooms and validity before the update. */
|
|
AkRoomID previousFront = GetFrontRoom();
|
|
AkRoomID previousBack = GetBackRoom();
|
|
/* Update the room connections */
|
|
FrontRoom = nullptr;
|
|
BackRoom = nullptr;
|
|
FAkAudioDevice * Dev = FAkAudioDevice::Get();
|
|
FindConnectedComponents(Dev->GetRoomIndex(), FrontRoom, BackRoom);
|
|
LastRoomsUpdate = GetWorld()->GetTimeSeconds();
|
|
PreviousLocation = GetComponentLocation();
|
|
PreviousRotation = GetComponentRotation();
|
|
const bool bRoomsChanged = GetFrontRoom() != previousFront || GetBackRoom() != previousBack;
|
|
#if WITH_EDITOR
|
|
if (bRoomsChanged)
|
|
UpdateRoomNames();
|
|
UpdateTextLocRotVis();
|
|
#endif
|
|
/* Return true if any room connection has changed. */
|
|
return bRoomsChanged;
|
|
}
|
|
|
|
UPrimitiveComponent* UAkPortalComponent::GetPrimitiveParent() const
|
|
{
|
|
return Parent;
|
|
}
|
|
|
|
FVector UAkPortalComponent::GetExtent() const
|
|
{
|
|
FBoxSphereBounds ComponentBounds = Bounds;
|
|
if (Parent != nullptr)
|
|
{
|
|
FTransform Transform (Parent->GetComponentTransform());
|
|
Transform.SetRotation(FQuat::Identity);
|
|
ComponentBounds = Parent->CalcBounds(Transform);
|
|
}
|
|
return ComponentBounds.BoxExtent;
|
|
}
|
|
|
|
AkRoomID UAkPortalComponent::GetFrontRoom() const { return FrontRoom == nullptr ? AkRoomID() : FrontRoom->GetRoomID(); }
|
|
AkRoomID UAkPortalComponent::GetBackRoom() const { return BackRoom == nullptr ? AkRoomID() : BackRoom->GetRoomID(); }
|
|
|
|
|
|
|
|
template <typename tComponent>
|
|
void UAkPortalComponent::FindConnectedComponents(FAkEnvironmentIndex& RoomIndex, tComponent*& out_pFront, tComponent*& out_pBack)
|
|
{
|
|
out_pFront = nullptr;
|
|
out_pBack = nullptr;
|
|
|
|
FAkAudioDevice* pAudioDevice = FAkAudioDevice::Get();
|
|
if (pAudioDevice != nullptr && Parent != nullptr)
|
|
{
|
|
float x = GetExtent().X;
|
|
FVector frontVector(x, 0.f, 0.f);
|
|
|
|
FTransform toWorld = Parent->GetComponentTransform();
|
|
toWorld.SetScale3D(FVector(1.0f));
|
|
|
|
FVector frontPoint = toWorld.TransformPosition(frontVector);
|
|
FVector backPoint = toWorld.TransformPosition(-1 * frontVector);
|
|
|
|
TArray<tComponent*> front = RoomIndex.Query<tComponent>(frontPoint, GetWorld());
|
|
if (front.Num() > 0)
|
|
out_pFront = front[0];
|
|
|
|
TArray<tComponent*> back = RoomIndex.Query<tComponent>(backPoint, GetWorld());
|
|
if (back.Num() > 0)
|
|
out_pBack = back[0];
|
|
}
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
bool UAkPortalComponent::AreTextVisualizersInitialized() const
|
|
{
|
|
return FrontRoomText != nullptr || BackRoomText != nullptr;
|
|
}
|
|
|
|
void UAkPortalComponent::InitTextVisualizers()
|
|
{
|
|
if (!HasAnyFlags(RF_Transient))
|
|
{
|
|
FString NamePrefix = GetOwner()->GetName() + GetName();
|
|
UMaterialInterface* mat = Cast<UMaterialInterface>(FAkAudioStyle::GetAkForegroundTextMaterial());
|
|
FrontRoomText = NewObject<UTextRenderComponent>(GetOuter(), *(NamePrefix + "_FrontRoomName"));
|
|
BackRoomText = NewObject<UTextRenderComponent>(GetOuter(), *(NamePrefix + "_BackRoomName"));
|
|
FrontRoomText->SetText(FText::FromString(""));
|
|
BackRoomText->SetText(FText::FromString(""));
|
|
TArray<UTextRenderComponent*> TextComponents{ FrontRoomText, BackRoomText };
|
|
for (UTextRenderComponent* Text : TextComponents)
|
|
{
|
|
if (mat != nullptr)
|
|
Text->SetTextMaterial(mat);
|
|
Text->RegisterComponentWithWorld(GetWorld());
|
|
Text->AttachToComponent(this, FAttachmentTransformRules::KeepWorldTransform);
|
|
Text->SetRelativeLocation(FVector(0.0f, 0.0f, 0.0f));
|
|
Text->bIsEditorOnly = true;
|
|
Text->bSelectable = true;
|
|
Text->bAlwaysRenderAsText = true;
|
|
Text->SetHorizontalAlignment(EHTA_Center);
|
|
Text->SetWorldScale3D(FVector(1.0f));
|
|
}
|
|
FrontRoomText->SetVerticalAlignment(EVRTA_TextTop);
|
|
BackRoomText->SetVerticalAlignment(EVRTA_TextBottom);
|
|
}
|
|
}
|
|
|
|
void UAkPortalComponent::DestroyTextVisualizers()
|
|
{
|
|
if (FrontRoomText != nullptr)
|
|
{
|
|
FrontRoomText->DestroyComponent();
|
|
FrontRoomText = nullptr;
|
|
}
|
|
if (BackRoomText != nullptr)
|
|
{
|
|
BackRoomText->DestroyComponent();
|
|
BackRoomText = nullptr;
|
|
}
|
|
}
|
|
|
|
void UAkPortalComponent::UpdateRoomNames()
|
|
{
|
|
if (!Parent || HasAnyFlags(RF_Transient) || !AreTextVisualizersInitialized())
|
|
return;
|
|
|
|
if (FrontRoomText != nullptr)
|
|
{
|
|
FrontRoomText->SetText(FText::FromString(""));
|
|
if (FrontRoom != nullptr)
|
|
FrontRoomText->SetText(FText::FromString(FrontRoom->GetRoomName()));
|
|
}
|
|
if (BackRoomText != nullptr)
|
|
{
|
|
BackRoomText->SetText(FText::FromString(""));
|
|
if (BackRoom != nullptr)
|
|
BackRoomText->SetText(FText::FromString(BackRoom->GetRoomName()));
|
|
}
|
|
}
|
|
|
|
void UAkPortalComponent::UpdateTextRotations() const
|
|
{
|
|
if (Parent == nullptr || !AreTextVisualizersInitialized())
|
|
return;
|
|
FVector BoxExtent = Parent->CalcBounds(FTransform()).BoxExtent;
|
|
const FTransform T = Parent->GetComponentTransform();
|
|
AkDrawBounds DrawBounds(T, BoxExtent);
|
|
// Setup the font normal to orient the text.
|
|
FVector Front = DrawBounds.FLD() - DrawBounds.BLD();
|
|
Front.Normalize();
|
|
FVector Up = DrawBounds.FLU() - DrawBounds.FLD();
|
|
Up.Normalize();
|
|
if (FrontRoomText != nullptr)
|
|
FrontRoomText->SetVerticalAlignment(EVRTA_TextTop);
|
|
if (BackRoomText != nullptr)
|
|
BackRoomText->SetVerticalAlignment(EVRTA_TextBottom);
|
|
if (FVector::DotProduct(FVector::UpVector, Up) < 0.0f)
|
|
{
|
|
if (FrontRoomText != nullptr)
|
|
FrontRoomText->SetVerticalAlignment(EVRTA_TextBottom);
|
|
if (BackRoomText != nullptr)
|
|
BackRoomText->SetVerticalAlignment(EVRTA_TextTop);
|
|
Up *= -1.0f;
|
|
}
|
|
// 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.
|
|
FVector CamToCentre;
|
|
if (GCurrentLevelEditingViewportClient != nullptr)
|
|
{
|
|
CamToCentre = GCurrentLevelEditingViewportClient->GetViewLocation() - Parent->Bounds.Origin;
|
|
if (FVector::DotProduct(CamToCentre, Front) < 0.0f)
|
|
Front *= -1.0f;
|
|
}
|
|
if (FrontRoomText != nullptr)
|
|
FrontRoomText->SetWorldRotation(UKismetMathLibrary::MakeRotFromXZ(Front, Up));
|
|
if (BackRoomText != nullptr)
|
|
BackRoomText->SetWorldRotation(UKismetMathLibrary::MakeRotFromXZ(Front, Up));
|
|
}
|
|
|
|
void UAkPortalComponent::UpdateTextVisibility()
|
|
{
|
|
if (!AreTextVisualizersInitialized())
|
|
return;
|
|
|
|
bool Visible = false;
|
|
if (GetWorld() != nullptr)
|
|
{
|
|
EWorldType::Type WorldType = GetWorld()->WorldType;
|
|
if (WorldType == EWorldType::Editor)
|
|
{
|
|
Visible = GetOwner() != nullptr && GetOwner()->IsSelected();
|
|
}
|
|
else if (WorldType == EWorldType::EditorPreview)
|
|
{
|
|
Visible = true;
|
|
}
|
|
}
|
|
if (GetOwner() != nullptr)
|
|
{
|
|
if (BackRoomText != nullptr)
|
|
BackRoomText->SetVisibility(Visible);
|
|
if (FrontRoomText != nullptr)
|
|
FrontRoomText->SetVisibility(Visible);
|
|
}
|
|
}
|
|
|
|
void UAkPortalComponent::UpdateTextLocRotVis()
|
|
{
|
|
if (Parent == nullptr || !AreTextVisualizersInitialized())
|
|
return;
|
|
FVector BoxExtent = Parent->CalcBounds(FTransform()).BoxExtent;
|
|
const FTransform T = Parent->GetComponentTransform();
|
|
AkDrawBounds DrawBounds(T, BoxExtent);
|
|
float PortalWidth = 0.0f;
|
|
FVector Right;
|
|
(DrawBounds.FRD() - DrawBounds.FLD()).ToDirectionAndLength(Right, PortalWidth);
|
|
// Setup the font normal to orient the text.
|
|
FVector Front = DrawBounds.FLD() - DrawBounds.BLD();
|
|
FVector Up = DrawBounds.FLU() - DrawBounds.FLD();
|
|
|
|
if (FrontRoomText != nullptr)
|
|
{
|
|
FrontRoomText->SetWorldScale3D(FVector(1.0f));
|
|
// Get a point at the top center of the local axis-aligned bounds, to position the front room text.
|
|
//FVector Top = Parent->Bounds.Origin + FVector(0.0f, 0.0f, Parent->Bounds.BoxExtent.Z);
|
|
FVector Top = Parent->Bounds.Origin;// +Front + Up;
|
|
// Add a depth offset so that the text sits out at the top front of the portal
|
|
Top += Front * 0.5f;
|
|
Top += Up * 0.5f;
|
|
FrontRoomText->SetWorldLocation(Top);
|
|
const FVector FrontTextWorldSize = FrontRoomText->GetTextWorldSize();
|
|
const float TextWidth = FrontTextWorldSize.GetAbsMax();
|
|
if (TextWidth > PortalWidth && PortalWidth > 0.0f)
|
|
FrontRoomText->SetWorldScale3D(FVector(PortalWidth / TextWidth));
|
|
}
|
|
if (BackRoomText != nullptr)
|
|
{
|
|
BackRoomText->SetWorldScale3D(FVector(1.0f));
|
|
// Get a point at the bottom center of the local axis-aligned bounds, to position the back room text.
|
|
//FVector Bottom = Parent->Bounds.Origin - FVector(0.0f, 0.0f, Parent->Bounds.BoxExtent.Z);
|
|
FVector Bottom = Parent->Bounds.Origin;
|
|
// Add a depth offset so that the text sits out at the bottom back of the portal
|
|
Bottom -= Front * 0.5f;
|
|
Bottom -= Up * 0.5f;
|
|
BackRoomText->SetWorldLocation(Bottom);
|
|
const FVector BackTextWorldSize = BackRoomText->GetTextWorldSize();
|
|
const float TextWidth = BackTextWorldSize.GetAbsMax();
|
|
if (TextWidth > PortalWidth && PortalWidth > 0.0f)
|
|
BackRoomText->SetWorldScale3D(FVector(PortalWidth / TextWidth));
|
|
}
|
|
UpdateTextRotations();
|
|
UpdateTextVisibility();
|
|
}
|
|
|
|
#endif
|
|
/*------------------------------------------------------------------------------------
|
|
AAkAcousticPortal
|
|
------------------------------------------------------------------------------------*/
|
|
|
|
AAkAcousticPortal::AAkAcousticPortal(const class FObjectInitializer& ObjectInitializer) :
|
|
Super(ObjectInitializer)
|
|
{
|
|
// Property initialization
|
|
static const FName CollisionProfileName(TEXT("OverlapAll"));
|
|
GetBrushComponent()->SetCollisionProfileName(CollisionProfileName);
|
|
|
|
bColored = true;
|
|
BrushColor = FColor(255, 196, 137, 255);
|
|
|
|
InitialState = AkAcousticPortalState::Open;
|
|
|
|
PrimaryActorTick.bCanEverTick = true;
|
|
PrimaryActorTick.TickGroup = TG_DuringPhysics;
|
|
PrimaryActorTick.bAllowTickOnDedicatedServer = false;
|
|
|
|
static const FName PortalComponentName = TEXT("PortalComponent");
|
|
Portal = ObjectInitializer.CreateDefaultSubobject<UAkPortalComponent>(this, PortalComponentName);
|
|
Portal->AttachToComponent(GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
|
|
|
|
#if WITH_EDITOR
|
|
CollisionChannel = EAkCollisionChannel::EAKCC_UseIntegrationSettingsDefault;
|
|
#endif
|
|
}
|
|
|
|
void AAkAcousticPortal::OpenPortal()
|
|
{
|
|
if (Portal != nullptr)
|
|
{
|
|
Portal->OpenPortal();
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAkAudio, Warning, TEXT("AAkAcousticPortal %s called OpenPortal with uninitialized portal component."), *GetName());
|
|
}
|
|
}
|
|
|
|
void AAkAcousticPortal::ClosePortal()
|
|
{
|
|
if (Portal != nullptr)
|
|
{
|
|
Portal->ClosePortal();
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAkAudio, Warning, TEXT("AAkAcousticPortal %s called ClosePortal with uninitialized portal component."), *GetName());
|
|
}
|
|
}
|
|
|
|
AkAcousticPortalState AAkAcousticPortal::GetCurrentState() const
|
|
{
|
|
if (Portal != nullptr)
|
|
return Portal->GetCurrentState();
|
|
UE_LOG(LogAkAudio, Warning, TEXT("AAkAcousticPortal %s called GetCurrentState with uninitialized portal component."), *GetName());
|
|
return AkAcousticPortalState::Closed;
|
|
}
|
|
|
|
AkRoomID AAkAcousticPortal::GetFrontRoom() const
|
|
{
|
|
if (Portal != nullptr)
|
|
return Portal->GetFrontRoom();
|
|
UE_LOG(LogAkAudio, Warning, TEXT("AAkAcousticPortal %s called GetFrontRoom with uninitialized portal component."), *GetName());
|
|
return AkRoomID();
|
|
}
|
|
|
|
AkRoomID AAkAcousticPortal::GetBackRoom() const
|
|
{
|
|
if (Portal != nullptr)
|
|
return Portal->GetBackRoom();
|
|
UE_LOG(LogAkAudio, Warning, TEXT("AAkAcousticPortal %s called GetBackRoom with uninitialized portal component."), *GetName());
|
|
return AkRoomID();
|
|
}
|
|
|
|
void AAkAcousticPortal::PostRegisterAllComponents()
|
|
{
|
|
Super::PostRegisterAllComponents();
|
|
|
|
if (bRequiresStateMigration)
|
|
{
|
|
if (Portal != nullptr)
|
|
{
|
|
Portal->InitialState = InitialState;
|
|
bRequiresStateMigration = false;
|
|
}
|
|
}
|
|
|
|
if (bRequiresTransformMigration)
|
|
{
|
|
FVector right = FVector(0.0f, 1.0f, 0.0f);
|
|
FVector left = FVector(0.0f, -1.0f, 0.0f);
|
|
FTransform actorTransform = GetActorTransform();
|
|
/* get the local 'front' (with respect to Y). */
|
|
FVector localYFront = (actorTransform.TransformPosition(right) - actorTransform.TransformPosition(left));
|
|
localYFront.Normalize();
|
|
FVector scale = GetActorScale3D();
|
|
SetActorScale3D(FVector(scale.Y, scale.X, scale.Z));
|
|
/* get the local front, using Unreal coordinate orientation. */
|
|
FVector localXFront = GetActorForwardVector();
|
|
/* get the local up vector around which to rotate. */
|
|
FVector localUp = FVector::CrossProduct(localYFront, localXFront);
|
|
/* rotate the local front vector around the local up, such that it points along the 'true' local front, in Unreal terms. */
|
|
localXFront = localXFront.RotateAngleAxis(-90.0f, localUp);
|
|
/* 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. */
|
|
SetActorRotation(UKismetMathLibrary::MakeRotFromXZ(localXFront, localUp));
|
|
|
|
bRequiresTransformMigration = false;
|
|
}
|
|
}
|
|
|
|
void AAkAcousticPortal::PostLoad()
|
|
{
|
|
Super::PostLoad();
|
|
const int32 AkVersion = GetLinkerCustomVersion(FAkCustomVersion::GUID);
|
|
|
|
if (AkVersion < FAkCustomVersion::SpatialAudioExtentAPIChange)
|
|
{
|
|
bRequiresTransformMigration = true;
|
|
}
|
|
|
|
if (AkVersion < FAkCustomVersion::SpatialAudioComponentisation)
|
|
{
|
|
bRequiresStateMigration = true;
|
|
}
|
|
}
|
|
|
|
void AAkAcousticPortal::Serialize(FArchive& Ar)
|
|
{
|
|
Ar.UsingCustomVersion(FAkCustomVersion::GUID);
|
|
Super::Serialize(Ar);
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
ECollisionChannel AAkAcousticPortal::GetCollisionChannel()
|
|
{
|
|
return UAkSettings::ConvertFitToGeomCollisionChannel(CollisionChannel.GetValue());
|
|
}
|
|
|
|
void AAkAcousticPortal::FitRaycast()
|
|
{
|
|
static const FName NAME_SAV_Fit = TEXT("AAkAcousticPortalRaycast");
|
|
|
|
UWorld* World = GEngine->GetWorldFromContextObjectChecked(this);
|
|
if (!World)
|
|
return;
|
|
|
|
TArray<TTuple<float, FVector, FVector>> hits;
|
|
|
|
// Ray length - DetectionRadius X current scale.
|
|
float RayLength = GetDetectionRadius();
|
|
|
|
FCollisionQueryParams CollisionParams(NAME_SAV_Fit, true, this);
|
|
|
|
FVector RaycastOrigin = bUseSavedRaycastOrigin ? SavedRaycastOrigin : GetActorLocation();
|
|
|
|
float Offset = 2.f / kNumRaycasts;
|
|
float Increment = PI * (3.f - sqrtf(5.f));
|
|
|
|
TArray< FHitResult > OutHits;
|
|
|
|
for (int i = 0; i < kNumRaycasts; ++i)
|
|
{
|
|
float x = ((i * Offset) - 1) + (Offset / 2);
|
|
float r = sqrtf(1.f - powf(x, 2.f));
|
|
|
|
float phi = ((i + 1) % kNumRaycasts) * Increment;
|
|
|
|
float y = cosf(phi) * r;
|
|
float z = sinf(phi) * r;
|
|
|
|
FVector to = RaycastOrigin + FVector(x, y, z) * RayLength;
|
|
|
|
OutHits.Empty();
|
|
World->LineTraceMultiByObjectType(OutHits, RaycastOrigin, to, (int)GetCollisionChannel(), CollisionParams);
|
|
|
|
if (OutHits.Num() > 0)
|
|
{
|
|
bool bHit = false;
|
|
FVector ImpactPoint0;
|
|
FVector ImpactNormal0;
|
|
|
|
for (auto& res : OutHits)
|
|
{
|
|
if (res.IsValidBlockingHit() &&
|
|
!AkSpatialAudioHelper::IsAkSpatialAudioActorClass(AkSpatialAudioHelper::GetActorFromHitResult(res)))
|
|
{
|
|
bHit = true;
|
|
ImpactPoint0 = res.ImpactPoint;
|
|
ImpactNormal0 = res.ImpactNormal;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bHit)
|
|
{
|
|
OutHits.Empty();
|
|
World->LineTraceMultiByObjectType(OutHits, ImpactPoint0, ImpactPoint0 + ImpactNormal0 * RayLength, (int)GetCollisionChannel(), CollisionParams);
|
|
|
|
bHit = false;
|
|
FVector ImpactPoint1;
|
|
|
|
for (auto& res : OutHits)
|
|
{
|
|
if (res.IsValidBlockingHit() &&
|
|
res.Distance > kMinPortalSize &&
|
|
!AkSpatialAudioHelper::IsAkSpatialAudioActorClass(AkSpatialAudioHelper::GetActorFromHitResult(res)))
|
|
{
|
|
bHit = true;
|
|
ImpactPoint1 = res.ImpactPoint;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bHit)
|
|
{
|
|
float distance = (ImpactPoint0 - ImpactPoint1).Size();
|
|
hits.Emplace(MakeTuple(distance, ImpactPoint0, ImpactPoint1));
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
auto SortPredicate = [](TTuple<float, FVector, FVector>& A, TTuple<float, FVector, FVector>& B) { return A.Get<0>() < B.Get<0>(); };
|
|
|
|
Algo::Sort(hits, SortPredicate);
|
|
|
|
static const float kDotEpsilon = 0.1f;
|
|
static const float kLineIntersectThresh = 2.0f;
|
|
|
|
float minDist = FLT_MAX;
|
|
int Best0 = INT_MAX;
|
|
int Best1 = INT_MAX;
|
|
bool bIntersects = false;
|
|
for (int i = 0; i < hits.Num() && !bIntersects; ++i)
|
|
{
|
|
FVector& pti = hits[i].Get<1>();
|
|
FVector vi = hits[i].Get<2>() - hits[i].Get<1>();
|
|
FVector diri;
|
|
float leni;
|
|
vi.ToDirectionAndLength(diri, leni);
|
|
|
|
for (int j = i + 1; j < hits.Num() && !bIntersects; ++j)
|
|
{
|
|
FVector& ptj = hits[j].Get<1>();
|
|
FVector vj = hits[j].Get<2>() - hits[j].Get<1>();
|
|
FVector dirj;
|
|
float lenj;
|
|
vj.ToDirectionAndLength(dirj, lenj);
|
|
|
|
if (FMath::Abs(FVector::DotProduct(diri, dirj)) < kDotEpsilon)
|
|
{
|
|
float proj_ji = FVector::DotProduct((ptj - pti), diri);
|
|
if (proj_ji > 0.f && proj_ji < leni)
|
|
{
|
|
float proj_ij = FVector::DotProduct((pti - ptj), dirj);
|
|
if (proj_ij > 0.f && proj_ij < lenj)
|
|
{
|
|
FVector p0 = pti + proj_ji * diri;
|
|
FVector p1 = ptj + proj_ij * dirj;
|
|
|
|
float dist = (p0 - p1).Size();
|
|
if (dist < minDist)
|
|
{
|
|
minDist = dist;
|
|
Best0 = i;
|
|
Best1 = j;
|
|
|
|
if (dist < kLineIntersectThresh)
|
|
{
|
|
//Assuming here we found a pretty good result, bail out so as to favor smaller portals over bigger ones.
|
|
bIntersects = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bIntersects)
|
|
{
|
|
BestFit[0] = hits[Best0].Get<1>();
|
|
BestFit[1] = hits[Best0].Get<2>();
|
|
BestFit[2] = hits[Best1].Get<1>();
|
|
BestFit[3] = hits[Best1].Get<2>();
|
|
|
|
BestFitValid = true;
|
|
}
|
|
else
|
|
{
|
|
// We will hold on to the best fit points, as long as they are within the detection radius.
|
|
BestFitValid = FVector::DistSquared(RaycastOrigin, (BestFit[0] + BestFit[1] + BestFit[2] + BestFit[3]) / 4.f) < DetectionRadius * DetectionRadius;
|
|
}
|
|
}
|
|
|
|
void AAkAcousticPortal::FitPortal()
|
|
{
|
|
if (!BestFitValid)
|
|
return;
|
|
|
|
FVector center;
|
|
FVector front;
|
|
FVector side;
|
|
FVector up;
|
|
FVector scale;
|
|
|
|
FVector& pti = BestFit[0];
|
|
FVector vi = BestFit[1] - BestFit[0];
|
|
FVector diri;
|
|
float leni;
|
|
vi.ToDirectionAndLength(diri, leni);
|
|
|
|
FVector& ptj = BestFit[2];
|
|
FVector vj = BestFit[3] - BestFit[2];
|
|
FVector dirj;
|
|
float lenj;
|
|
vj.ToDirectionAndLength(dirj, lenj);
|
|
|
|
float proj_ji = FVector::DotProduct((ptj - pti), diri);
|
|
if (proj_ji > 0.f && proj_ji < leni)
|
|
{
|
|
float proj_ij = FVector::DotProduct((pti - ptj), dirj);
|
|
if (proj_ij > 0.f && proj_ij < lenj)
|
|
{
|
|
FVector p0 = pti + proj_ji * diri;
|
|
FVector p1 = ptj + proj_ij * dirj;
|
|
|
|
center = pti - proj_ij * dirj;
|
|
center += diri * leni / 2.f + dirj * lenj / 2.f;
|
|
|
|
front = FVector::CrossProduct(diri, dirj);
|
|
side = diri;
|
|
up = dirj;
|
|
scale.Y = leni / 2.f;
|
|
scale.Z = lenj / 2.f;
|
|
|
|
scale /= kDefaultBrushExtents;
|
|
|
|
scale.X = GetActorScale3D().X;
|
|
|
|
auto* RC = GetRootComponent();
|
|
if (RC)
|
|
{
|
|
RC->SetWorldLocation(center);
|
|
FRotator rotation = FRotationMatrix::MakeFromXZ(front, up).Rotator();
|
|
RC->SetWorldRotation(rotation);
|
|
RC->SetWorldScale3D(scale);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void AAkAcousticPortal::PostEditMove(bool bFinished)
|
|
{
|
|
Super::PostEditMove(bFinished);
|
|
|
|
if (FitToGeometry)
|
|
{
|
|
FitRaycast();
|
|
|
|
IsDragging = !bFinished;
|
|
|
|
if (bFinished)
|
|
{
|
|
bUseSavedRaycastOrigin = false;
|
|
|
|
FitPortal();
|
|
}
|
|
}
|
|
}
|
|
|
|
void AAkAcousticPortal::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
|
|
{
|
|
Super::PostEditChangeProperty(PropertyChangedEvent);
|
|
|
|
if (PropertyChangedEvent.Property)
|
|
{
|
|
if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(AAkAcousticPortal, FitToGeometry))
|
|
{
|
|
ClearBestFit();
|
|
|
|
if (FitToGeometry)
|
|
{
|
|
FitRaycast();
|
|
FitPortal();
|
|
}
|
|
}
|
|
|
|
if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(AAkAcousticPortal, DetectionRadius))
|
|
{
|
|
if (FitToGeometry)
|
|
{
|
|
if (!bUseSavedRaycastOrigin)
|
|
{
|
|
// Cache the actor position to get consistant results over multiple updates, since FitPortal() changes the actor location.
|
|
SavedRaycastOrigin = GetActorLocation();
|
|
bUseSavedRaycastOrigin = true;
|
|
}
|
|
|
|
FitRaycast();
|
|
FitPortal();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void AAkAcousticPortal::ClearBestFit()
|
|
{
|
|
BestFit[0] = FVector::ZeroVector;
|
|
BestFit[1] = FVector::ZeroVector;
|
|
BestFit[2] = FVector::ZeroVector;
|
|
BestFit[3] = FVector::ZeroVector;
|
|
BestFitValid = false;
|
|
}
|
|
|
|
#endif // WITH_EDITOR
|
|
|