Files
2025-05-18 13:04:45 +08:00

1107 lines
35 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SoundscapeSubsystem.h"
#include "DrawDebugHelpers.h"
#include "Engine/GameInstance.h"
#include "Engine/World.h"
#include "SoundscapeColorPoint.h"
#include "SoundscapeSettings.h"
#include "SoundScapePalette.h"
#include "AudioDevice.h"
#include "TimerManager.h"
#include "Engine/AssetManager.h"
#include "Engine/StreamableManager.h"
static FAutoConsoleCommandWithWorld GResetSoundscape(
TEXT("soundscape.ResetSoundscape"),
TEXT("Forces restart on all active Soundscape Palettes."),
FConsoleCommandWithWorldDelegate::CreateLambda([](UWorld* World)
{
if (!World)
{
return;
}
if (const UGameInstance* GameInstance = World->GetGameInstance())
{
if (USoundscapeSubsystem* SoundscapeSubsystem = GameInstance->GetSubsystem<USoundscapeSubsystem>())
{
SoundscapeSubsystem->RestartSoundscape();
}
}
}));
void USoundscapeColorPointHashMap::InitializeHash(float HashCellSizeIn, FVector GridCenterIn)
{
// Cache Hash Cell Size
HashCellSize = FMath::Max(HashCellSizeIn, 1.0f);
// MaxGridWidth / Hash Cell Size
GridWidth = FMath::FloorToInt(MaxGridWidth / HashCellSize);
// Grid Width Cubed
NumCells = int64(GridWidth) * int64(GridWidth) * int64(GridWidth);
// Cell Size Inverted
HashCellFactor = 1 / HashCellSize;
// Cache Grid Centerpoint
GridCenter = GridCenterIn;
// Calculate Grid Origin Offset
GridOriginOffset = GridCenter - FVector(float(HalfMaxGridWidth), float(HalfMaxGridWidth), float(HalfMaxGridWidth));
}
void USoundscapeColorPointHashMap::ClearHash()
{
// Cycle through Color Point Hash Map
for (auto It = ColorPointHashMap.CreateIterator(); It; ++It)
{
// If Color Point has an existing hash map, empty it
if (USoundscapeColorPointHashCellDensity* ColorPointHashCell = It->Value)
{
ColorPointHashCell->ColorPointHashCellDensity.Empty();
}
}
// Empty Hash Map
ColorPointHashMap.Empty();
}
int32 USoundscapeColorPointHashMap::NumColorPointsInCell(const FVector& Location, const FGameplayTag ColorPoint)
{
// Determine if the Hash Map contains the Color Point Density Table Pointer
if (TObjectPtr<USoundscapeColorPointHashCellDensity>* ColorPointHashCellPtr = ColorPointHashMap.Find(ColorPoint))
{
// Validate the double pointer
if (USoundscapeColorPointHashCellDensity* ColorPointHashCell = *ColorPointHashCellPtr)
{
// Validate the Color Point Density Map
if (ColorPointHashCell != nullptr)
{
// Look up Color Point Density at that Location
int32* CellDensity = ColorPointHashCell->ColorPointHashCellDensity.Find(CalculateHashIndex(Location));
// If Cell Density pointer is valid, return value
if (CellDensity != nullptr)
{
return *CellDensity;
}
}
}
}
// Otherwise return 0
return 0;
}
bool USoundscapeColorPointHashMap::AddColorPointToHash(const FVector& Location, const FGameplayTag ColorPoint)
{
// Add a single Color Point to the Color Point Hash Map
// If the Color Point Coordinates exceed the Max Grid Width, it is out of bounds of the hash table
// Incoming Location values are added to the Grid Origin Offset in the case that the Grid is not centered around Origin 0,0,0
if (FMath::Abs(GridCenter.X + Location.X) > HalfMaxGridWidth
|| FMath::Abs(GridCenter.Y + Location.Y) > HalfMaxGridWidth
|| FMath::Abs(GridCenter.Z + Location.Z) > HalfMaxGridWidth)
{
UE_LOG(LogSoundscapeSubsystem, Warning, TEXT("Location out of Hash Bounds"));
// Return false, unable to add Color Point to Hash
return false;
}
// Get ColorPoint Density Map
USoundscapeColorPointHashCellDensity* ColorPointDensityMap = GetColorPointDensityMap(ColorPoint);
// Create a new Color Point Hash for this Color Point if one needs to be created
if (ColorPointDensityMap == nullptr)
{
// Create new Color Point Density Map
ColorPointDensityMap = NewObject<USoundscapeColorPointHashCellDensity>(this);
// Add to Color Point Hash Map
ColorPointHashMap.Add(ColorPoint, ColorPointDensityMap);
}
// If a Color Point Map exists for this Color Point, use it
if (ColorPointDensityMap)
{
// Cache Hash Index
uint64 HashIndex = CalculateHashIndex(Location);
// Get potential Density value from Hash Index
int32* ColorPointDensity = ColorPointDensityMap->ColorPointHashCellDensity.Find(HashIndex);
// Validate Density pointer
if (ColorPointDensity != nullptr)
{
// Color Point already tracked at this hash cell, increment
++(*ColorPointDensity);
}
else
{
// Density Value invalid, new Hash Key/Value must be added at that index
ColorPointDensityMap->ColorPointHashCellDensity.Add(HashIndex, 1);
}
}
// Color Point successfully added
return true;
}
void USoundscapeColorPointHashMap::AddColorPointArrayToHash(const TArray<FVector>& Locations, const FGameplayTag ColorPoint)
{
// Local cache for Hash Indices
TArray<uint64> HashIndices;
// Get ColorPoint Density Map
USoundscapeColorPointHashCellDensity* ColorPointDensityMap = GetColorPointDensityMap(ColorPoint);
// Create a new Color Point Hash for this Color Point if one needs to be created
if (ColorPointDensityMap == nullptr)
{
// Create new Color Point Density Map
ColorPointDensityMap = NewObject<USoundscapeColorPointHashCellDensity>(this);
// Add to Color Point Hash Map
ColorPointHashMap.Add(ColorPoint, ColorPointDensityMap);
}
// If a Color Point Map exists for this Color Point, use it
if(ColorPointDensityMap)
{
// Cycle through input Locations, add to temp array the indices calculated by the hashing function
for (auto IndividualLocation = Locations.CreateConstIterator(); IndividualLocation; ++IndividualLocation)
{
// Make sure incoming Location Vector is within Hash Map Bounds
if (FMath::Abs(GridCenter.X + IndividualLocation->X) <= HalfMaxGridWidth
&& FMath::Abs(GridCenter.Y + IndividualLocation->Y) <= HalfMaxGridWidth
&& FMath::Abs(GridCenter.Z + IndividualLocation->Z) <= HalfMaxGridWidth)
{
// Add Hash Index to temp Array
HashIndices.Add(CalculateHashIndex(*IndividualLocation));
}
else
{
// If Location out of Hash Map Bounds, throw it away and spit out a warning
UE_LOG(LogSoundscapeSubsystem, Warning, TEXT("Location out of Hash Bounds"));
}
}
// Cycle through temp array of hash indices
for (auto HashIndex = HashIndices.CreateConstIterator(); HashIndex; ++HashIndex)
{
// Cache index
uint64 NewKey = *HashIndex;
// Look for Color Point Density at that Index
int32* ColorPointDensity = ColorPointDensityMap->ColorPointHashCellDensity.Find(NewKey);
// If Color Point Density is valid,
if (ColorPointDensity != nullptr)
{
// Increment the value at the pointer
++(*ColorPointDensity);
}
else
{
// Add a new Cell
ColorPointDensityMap->ColorPointHashCellDensity.Add(NewKey, 1);
}
}
}
}
uint64 USoundscapeColorPointHashMap::CalculateHashIndex(const FVector& Location)
{
const FVector OffsetLocation = Location - GridOriginOffset;
uint64 HashX = uint64(FMath::FloorToInt(OffsetLocation.X * HashCellFactor));
uint64 HashY = uint64(FMath::FloorToInt(OffsetLocation.Y * HashCellFactor)) * uint64(GridWidth);
uint64 HashZ = uint64(FMath::FloorToInt(OffsetLocation.Z * HashCellFactor)) * (uint64(GridWidth) * uint64(GridWidth));
return HashX + HashY + HashZ;
}
void USoundscapeColorPointHashMap::SetGridCenterpoint(const FVector& Location)
{
// Cache new Centerpoint
GridCenter = Location;
// Calculate Grid Origin Offset
GridOriginOffset = Location - FVector(float(HalfMaxGridWidth), float(HalfMaxGridWidth), float(HalfMaxGridWidth));
}
TMap<FGameplayTag, int32> USoundscapeColorPointHashMap::GetHashMapColorPointDensitySummary()
{
TMap<FGameplayTag, int32> DensitySummary;
for (auto It = ColorPointHashMap.CreateConstIterator(); It; ++It)
{
FGameplayTag ColorPoint = It.Key();
if (ColorPoint.IsValid())
{
int32 ColorDensity = 0;
if (USoundscapeColorPointHashCellDensity* ColorPointHashLayer = It.Value())
{
for (auto Jt = ColorPointHashLayer->ColorPointHashCellDensity.CreateConstIterator(); Jt; ++Jt)
{
ColorDensity += Jt.Value();
}
}
DensitySummary.Add(ColorPoint, ColorDensity);
}
}
return DensitySummary;
}
USoundscapeColorPointHashCellDensity* USoundscapeColorPointHashMap::GetColorPointDensityMap(FGameplayTag ColorPoint)
{
if (TObjectPtr<USoundscapeColorPointHashCellDensity>* ColorPointHashCellPtr = ColorPointHashMap.Find(ColorPoint))
{
// Get Color Point Hash Cell Pointer
return *ColorPointHashCellPtr;
}
return nullptr;
}
const FVector USoundscapeColorPointHashMap::GetHashCellCenterpoint(FVector Location)
{
const int32 LocationX = FMath::FloorToInt((Location.X - GridOriginOffset.X) / HashCellSize);
const int32 LocationY = FMath::FloorToInt((Location.Y - GridOriginOffset.Y) / HashCellSize);
const int32 LocationZ = FMath::FloorToInt((Location.Z - GridOriginOffset.Z) / HashCellSize);
const float HalfHashCellSize = HashCellSize * 0.5;
return FVector(((float)LocationX * HashCellSize) + HalfHashCellSize + GridOriginOffset.X
, ((float)LocationY * HashCellSize) + HalfHashCellSize + GridOriginOffset.Y
, ((float)LocationZ * HashCellSize) + HalfHashCellSize + GridOriginOffset.Z);
}
const float USoundscapeColorPointHashMap::GetHashCellWidth()
{
return HashCellSize;
}
void USoundscapeColorPointHashMapCollection::InitializeCollection()
{
// Get plugin settings on Subsystem initialization
if (const USoundscapeSettings* ProjectSettings = GetDefault<USoundscapeSettings>())
{
// Hash Cell Width for LOD1
LOD1ColorPointHashWidth = FMath::Max(ProjectSettings->LOD1ColorPointHashWidth, 1.0f);
// Hash Cell LOD1 Max Distance
LOD1ColorPointHashDistance = FMath::Max(ProjectSettings->LOD1ColorPointHashDistance, 1.0f);
// Hash Cell Width for LOD2
LOD2ColorPointHashWidth = FMath::Max(ProjectSettings->LOD2ColorPointHashWidth, 1.0f);
// Hash Cell LOD2 Max Distance
LOD2ColorPointHashDistance = FMath::Max(ProjectSettings->LOD2ColorPointHashDistance, 1.0f);
// Hash Cell Width for LOD3
LOD3ColorPointHashWidth = FMath::Max(ProjectSettings->LOD3ColorPointHashWidth, 1.0f);
}
// Initialize LOD Hashes
ColorPointHashMapLOD1 = NewObject<USoundscapeColorPointHashMap>(this);
ColorPointHashMapLOD1->InitializeHash(LOD1ColorPointHashWidth, FVector::ZeroVector);
ColorPointHashMapLOD2 = NewObject<USoundscapeColorPointHashMap>(this);
ColorPointHashMapLOD2->InitializeHash(LOD2ColorPointHashWidth, FVector::ZeroVector);
ColorPointHashMapLOD3 = NewObject<USoundscapeColorPointHashMap>(this);
ColorPointHashMapLOD3->InitializeHash(LOD3ColorPointHashWidth, FVector::ZeroVector);
}
void USoundscapeColorPointHashMapCollection::AddColorPointArrayToHashMapCollection(const TArray<FVector>& Locations, const FGameplayTag ColorPoint)
{
if (ColorPointHashMapLOD1)
{
ColorPointHashMapLOD1->AddColorPointArrayToHash(Locations, ColorPoint);
}
if (ColorPointHashMapLOD2)
{
ColorPointHashMapLOD2->AddColorPointArrayToHash(Locations, ColorPoint);
}
if (ColorPointHashMapLOD3)
{
ColorPointHashMapLOD3->AddColorPointArrayToHash(Locations, ColorPoint);
}
}
void USoundscapeColorPointHashMapCollection::ClearColorPointHashMapCollection()
{
if (ColorPointHashMapLOD1)
{
ColorPointHashMapLOD1->ClearHash();
}
if (ColorPointHashMapLOD2)
{
ColorPointHashMapLOD2->ClearHash();
}
if (ColorPointHashMapLOD3)
{
ColorPointHashMapLOD3->ClearHash();
}
}
int32 USoundscapeColorPointHashMapCollection::GetColorPointHashMapCollectionDensity(const FVector Location, const FGameplayTag ColorPoint, const ESoundscapeLOD SoundscapeLOD)
{
if (ColorPointHashMapLOD1 && ColorPointHashMapLOD2 && ColorPointHashMapLOD3)
{
switch (SoundscapeLOD)
{
case ESoundscapeLOD::LOD1:
return ColorPointHashMapLOD1->NumColorPointsInCell(Location, ColorPoint);
case ESoundscapeLOD::LOD2:
return ColorPointHashMapLOD2->NumColorPointsInCell(Location, ColorPoint);
case ESoundscapeLOD::LOD3:
return ColorPointHashMapLOD3->NumColorPointsInCell(Location, ColorPoint);
}
}
return 0;
}
void USoundscapeColorPointHashMapCollection::CalculateTotalColorPointDensity(TMap<FGameplayTag, int32>& TotalHashMapColorPointDensity, ESoundscapeLOD SoundscapeLOD)
{
if (ColorPointHashMapLOD1 && ColorPointHashMapLOD2 && ColorPointHashMapLOD3)
{
switch (SoundscapeLOD)
{
case ESoundscapeLOD::LOD1:
TotalHashMapColorPointDensity = ColorPointHashMapLOD1->GetHashMapColorPointDensitySummary();
case ESoundscapeLOD::LOD2:
TotalHashMapColorPointDensity = ColorPointHashMapLOD2->GetHashMapColorPointDensitySummary();
case ESoundscapeLOD::LOD3:
TotalHashMapColorPointDensity = ColorPointHashMapLOD3->GetHashMapColorPointDensitySummary();
}
}
}
void USoundscapeSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
UE_LOG(LogSoundscapeSubsystem, Verbose, TEXT("Subsystem Initializing"));
// Get Subsystem World
UWorld* World = GetWorld();
// Initialize AudioDeviceID
AudioDeviceID = INDEX_NONE;
// Get world
if (World)
{
// Get Audio Device Handle
FAudioDevice* AudioDevice = World->GetAudioDeviceRaw();
if (AudioDevice)
{
AudioDeviceID = AudioDevice->DeviceID;
}
}
// Get plugin settings on Subsystem initialization
if (const USoundscapeSettings* ProjectSettings = GetDefault<USoundscapeSettings>())
{
if (ProjectSettings->SoundscapePaletteCollection.IsEmpty() == false)
{
FSoundscapePaletteCollection PaletteCollectionToAdd;
PaletteCollectionToAdd.SoundscapePaletteCollection = ProjectSettings->SoundscapePaletteCollection;
UE_LOG(LogSoundscapeSubsystem, Verbose, TEXT("Project settings Soundscape Palette Collection is valid"));
AddPaletteCollection(FName("GlobalPaletteCollection"), PaletteCollectionToAdd);
}
else
{
UE_LOG(LogSoundscapeSubsystem, Verbose, TEXT("Project settings Soundscape Palette Collection is empty"));
}
bDebugMode = ProjectSettings->bDebugDraw;
// Hash Cell Width for LOD1
LOD1ColorPointHashWidth = FMath::Max(ProjectSettings->LOD1ColorPointHashWidth, 1.0f);
// Hash Cell LOD1 Max Distance
LOD1ColorPointHashDistance = FMath::Max(ProjectSettings->LOD1ColorPointHashDistance, 1.0f);
// Hash Cell Width for LOD2
LOD2ColorPointHashWidth = FMath::Max(ProjectSettings->LOD2ColorPointHashWidth, 1.0f);
// Hash Cell LOD2 Max Distance
LOD2ColorPointHashDistance = FMath::Max(ProjectSettings->LOD2ColorPointHashDistance, 1.0f);
// Hash Cell Width for LOD3
LOD3ColorPointHashWidth = FMath::Max(ProjectSettings->LOD3ColorPointHashWidth, 1.0f);
// Hash Cell Width for the Active Hash
ActiveColorPointHashWidth = FMath::Max(ProjectSettings->ActiveColorPointHashWidth, 1.0f);
// Hash Cell Update Timing for the Active Hash
ActiveColorPointHashUpdateTimeSeconds = FMath::Max(ProjectSettings->ActiveColorPointHashUpdateTimeSeconds, 0.2f);
}
else
{
UE_LOG(LogSoundscapeSubsystem, Verbose, TEXT("Invalid Project Settings"));
}
// Initialize LOD Hashes
ColorPointHashMapLOD1 = NewObject<USoundscapeColorPointHashMap>(this);
ColorPointHashMapLOD1->InitializeHash(LOD1ColorPointHashWidth, FVector::ZeroVector);
ColorPointHashMapLOD2 = NewObject<USoundscapeColorPointHashMap>(this);
ColorPointHashMapLOD2->InitializeHash(LOD2ColorPointHashWidth, FVector::ZeroVector);
ColorPointHashMapLOD3 = NewObject<USoundscapeColorPointHashMap>(this);
ColorPointHashMapLOD3->InitializeHash(LOD3ColorPointHashWidth, FVector::ZeroVector);
// Initialize Active Hash Map
ActiveColorPointHashMap = NewObject<USoundscapeColorPointHashMap>(this);
ActiveColorPointHashMap->InitializeHash(ActiveColorPointHashWidth, FVector::ZeroVector);
UpdateActiveColorPointHashMap();
}
void USoundscapeSubsystem::Deinitialize()
{
UE_LOG(LogSoundscapeSubsystem, Verbose, TEXT("Subsystem Deinitializing"));
ActiveColorPointUpdateTimer.Invalidate();
for(const TPair<FName, TSharedPtr<FStreamableHandle>>& HandleSharedPtr : ActiveAssetLoadHandles)
{
if(HandleSharedPtr.Value.IsValid())
{
if (FStreamableHandle* HandlePtr = HandleSharedPtr.Value.Get())
{
HandlePtr->CancelHandle();
}
}
}
}
bool USoundscapeSubsystem::ShouldCreateSubsystem(UObject* Outer) const
{
// Check with parent first
if (Super::ShouldCreateSubsystem(Outer) == false)
{
UE_LOG(LogSoundscapeSubsystem, Verbose, TEXT("Subsystem will not be created based on parent rules"));
return false;
}
// Get World
if (UWorld* OuterWorld = Outer->GetWorld())
{
if (FAudioDevice* OuterAudioDevice = OuterWorld->GetAudioDeviceRaw())
{
UE_LOG(LogSoundscapeSubsystem, Verbose, TEXT("Audio device valid, will create subsystem"));
// If we do have an audio device, we can create this subsystem.
return true;
}
}
UE_LOG(LogSoundscapeSubsystem, Verbose, TEXT("Subsystem will not be created"));
// If we do not have an audio device, we do not need to create this subsystem.
return false;
}
void USoundscapeSubsystem::SetState(FGameplayTag SoundscapeState)
{
if (SoundscapeState.IsValid())
{
// Add new state to the container
SubsystemState.AddLeafTag(SoundscapeState);
UE_LOG(LogSoundscapeSubsystem, Verbose, TEXT("Set State called"));
UpdateState();
}
else
{
UE_LOG(LogSoundscapeSubsystem, Warning, TEXT("Set State called with invalid State Tag"));
}
}
void USoundscapeSubsystem::ClearState(FGameplayTag SoundscapeState)
{
if (SoundscapeState.IsValid())
{
// Add new state to the container
SubsystemState.RemoveTag(SoundscapeState);
UE_LOG(LogSoundscapeSubsystem, Verbose, TEXT("Clear State called"));
UpdateState();
}
else
{
UE_LOG(LogSoundscapeSubsystem, Verbose, TEXT("Clear State called with invalid State Tag"));
}
}
void USoundscapeSubsystem::RestartSoundscape()
{
// Restart matching valid Soundscape Palettes
for (const TPair<USoundscapePalette*, UActiveSoundscapePalette*> ActiveSoundscapePalettePair : ActivePalettes)
{
USoundscapePalette* SoundscapePalette = ActiveSoundscapePalettePair.Key;
UActiveSoundscapePalette* ActiveSoundscapePalette = ActiveSoundscapePalettePair.Value;
if (SoundscapePalette && ActiveSoundscapePalette)
{
ActiveSoundscapePalette->Stop();
ActiveSoundscapePalette->Play();
}
}
}
bool USoundscapeSubsystem::AddPaletteCollection(FName PaletteCollectionName, FSoundscapePaletteCollection PaletteCollection)
{
if (UnloadedPaletteCollections.Contains(PaletteCollectionName) == false)
{
UnloadedPaletteCollections.Add(PaletteCollectionName, PaletteCollection);
UE_LOG(LogSoundscapeSubsystem, Verbose, TEXT("Palette Collection %s added"), *PaletteCollectionName.ToString());
bool bLoaded = LoadPaletteCollection(PaletteCollectionName);
UpdateState();
return bLoaded;
}
UE_LOG(LogSoundscapeSubsystem, Verbose, TEXT("Add Palette Collection called with empty Palette Collection"));
return false;
}
bool USoundscapeSubsystem::RemovePaletteCollection(FName PaletteCollectionName)
{
if (UnloadedPaletteCollections.Find(PaletteCollectionName))
{
if (UnloadPaletteCollection(PaletteCollectionName))
{
// Release the handle pointer if Palette Collection still loading
if (TSharedPtr<FStreamableHandle>* AssetHandlePtr = ActiveAssetLoadHandles.Find(PaletteCollectionName))
{
TSharedPtr<FStreamableHandle> AssetHandle = *AssetHandlePtr;
if (AssetHandle.IsValid())
{
AssetHandle.Get()->CancelHandle();
AssetHandle.Reset();
}
ActiveAssetLoadHandles.Remove(PaletteCollectionName);
}
// Unloaded, now remove
UnloadedPaletteCollections.Remove(PaletteCollectionName);
UE_LOG(LogSoundscapeSubsystem, Verbose, TEXT("Palette Collection %s removed"), *PaletteCollectionName.ToString());
UpdateState();
return true;
}
}
UE_LOG(LogSoundscapeSubsystem, Verbose, TEXT("Remove Palette Collection called, but the name could not be found"));
return false;
}
bool USoundscapeSubsystem::LoadPaletteCollection(FName PaletteCollectionName)
{
if (UnloadedPaletteCollections.Contains(PaletteCollectionName))
{
UE_LOG(LogSoundscapeSubsystem, Verbose, TEXT("Attempting to load Palette Collection %s"), *PaletteCollectionName.ToString());
FSoundscapePaletteCollection PaletteCollectionToLoad = UnloadedPaletteCollections.FindRef(PaletteCollectionName);
// Early out as false if Collection Empty
if (PaletteCollectionToLoad.SoundscapePaletteCollection.Num() <= 0)
{
UE_LOG(LogSoundscapeSubsystem, Verbose, TEXT("Attempted to load Palette Collection %s but Collection Empty"), *PaletteCollectionName.ToString());
return false;
}
// Set up Asset Path Array
TArray<FSoftObjectPath> AssetsToLoad;
AssetsToLoad.Reserve(PaletteCollectionToLoad.SoundscapePaletteCollection.Num());
// Copy paths over to array
for (FSoftObjectPath AssetPath : PaletteCollectionToLoad.SoundscapePaletteCollection)
{
AssetsToLoad.Add(AssetPath);
}
// Create a callback delegate for completing the async load request
FStreamableDelegate AssetLoadingDelegate = FStreamableDelegate::CreateUObject(this, &USoundscapeSubsystem::CompleteAsyncLoading, PaletteCollectionName);
// Set up shared pointer for Asset Manager Async Load Request
TSharedPtr<FStreamableHandle> AssetHandle;
AssetHandle = UAssetManager::GetStreamableManager().RequestAsyncLoad(MoveTemp(AssetsToLoad), MoveTemp(AssetLoadingDelegate));
// Add Shared Pointer to map, allowing multiple load calls in a single frame, if necessary
ActiveAssetLoadHandles.Add(PaletteCollectionName, AssetHandle);
return true;
}
UE_LOG(LogSoundscapeSubsystem, Verbose, TEXT("Attempted to load Palette Collection %s but failed"), *PaletteCollectionName.ToString());
return false;
}
void USoundscapeSubsystem::CompleteAsyncLoading(FName PaletteCollectionName)
{
FSoundscapePaletteCollection PaletteCollectionToLoad = UnloadedPaletteCollections.FindRef(PaletteCollectionName);
for (FSoftObjectPath& ObjPath : PaletteCollectionToLoad.SoundscapePaletteCollection)
{
if (UObject* Object = ObjPath.ResolveObject())
{
USoundscapePalette* SoundscapePalette = Cast<USoundscapePalette>(Object);
if (SoundscapePalette)
{
// If palette is valid, add it to the Subsystem Collection
LoadedPaletteCollectionSet.Add(SoundscapePalette);
UE_LOG(LogSoundscapeSubsystem, Verbose, TEXT("Valid Soundscape Palette %s in Palette Collection %s"), *SoundscapePalette->GetFullName(), *PaletteCollectionName.ToString());
}
else
{
UE_LOG(LogSoundscapeSubsystem, Verbose, TEXT("Invalid Soundscape Palette in Palette Collection %s"), *PaletteCollectionName.ToString());
}
}
else
{
UE_LOG(LogSoundscapeSubsystem, Verbose, TEXT("Failed to load %s"), *ObjPath.ToString());
}
}
// Release the handle pointer
if (TSharedPtr<FStreamableHandle>* AssetHandlePtr = ActiveAssetLoadHandles.Find(PaletteCollectionName))
{
TSharedPtr<FStreamableHandle> AssetHandle = *AssetHandlePtr;
if (AssetHandle.IsValid())
{
AssetHandle.Reset();
}
ActiveAssetLoadHandles.Remove(PaletteCollectionName);
}
}
bool USoundscapeSubsystem::UnloadPaletteCollection(FName PaletteCollectionName)
{
if (UnloadedPaletteCollections.Find(PaletteCollectionName))
{
UE_LOG(LogSoundscapeSubsystem, Verbose, TEXT("Attempting to unload Palette Collection %s"), *PaletteCollectionName.ToString());
// Get unloaded set to remove from list
FSoundscapePaletteCollection PaletteCollectionToUnload = UnloadedPaletteCollections.FindRef(PaletteCollectionName);
TSet<FSoftObjectPath> PaletteCollectionSetToUnload;
PaletteCollectionSetToUnload.Append(PaletteCollectionToUnload.SoundscapePaletteCollection);
TSet<FSoftObjectPath> PaletteCollectionSetToKeep;
// Go through all unloaded collections and construct a unique list of Soft Object Paths
for (TPair<FName, FSoundscapePaletteCollection>& UnloadedCollectionName : UnloadedPaletteCollections)
{
if (UnloadedCollectionName.Key != PaletteCollectionName)
{
PaletteCollectionSetToKeep.Append(UnloadedCollectionName.Value.SoundscapePaletteCollection.Intersect(PaletteCollectionSetToUnload));
}
}
// Find the difference between what we want to retain and what we want to unload
PaletteCollectionSetToUnload = PaletteCollectionSetToUnload.Difference(PaletteCollectionSetToKeep);
// Temp set of palettes to remove and stop
decltype(LoadedPaletteCollectionSet) PalettesToUnloadStopAndRemove;
// Go through collection, get their loaded pointers, add to temp removal set
for (FSoftObjectPath& ObjPath : PaletteCollectionSetToUnload)
{
if (UObject* PalettePath = ObjPath.TryLoad())
{
USoundscapePalette* SoundscapePalette = Cast<USoundscapePalette>(PalettePath);
if (SoundscapePalette)
{
// If palette is valid, add it to the Subsystem Collection
PalettesToUnloadStopAndRemove.Add(SoundscapePalette);
}
}
}
// Stop and remove from Active list
for (USoundscapePalette* PaletteKeyToStop : PalettesToUnloadStopAndRemove)
{
if (PaletteKeyToStop)
{
if (UActiveSoundscapePalette* ActiveSoundscapePalette = ActivePalettes.FindRef(PaletteKeyToStop))
{
ActiveSoundscapePalette->Stop();
ActivePalettes.Remove(PaletteKeyToStop);
UE_LOG(LogSoundscapeSubsystem, Verbose, TEXT("Soundscape Palette %s stopped"), *ActiveSoundscapePalette->GetFullName());
}
}
}
// Remove from main list of loaded palettes
LoadedPaletteCollectionSet = LoadedPaletteCollectionSet.Difference(PalettesToUnloadStopAndRemove);
UE_LOG(LogSoundscapeSubsystem, Verbose, TEXT("Palette Collection %s unloaded"), *PaletteCollectionName.ToString());
return true;
}
UE_LOG(LogSoundscapeSubsystem, Verbose, TEXT("Attempted to unload Palette Collection %s but could not find matching collection"), *PaletteCollectionName.ToString());
return false;
}
void USoundscapeSubsystem::AddColorPointCollection(FName ColorPointCollectionName, FSoundscapeColorPointCollection ColorPointCollection)
{
ColorPointCollections.Add(ColorPointCollectionName, ColorPointCollection);
UpdateColorPointHashMap(*ColorPointHashMapLOD1);
UpdateColorPointHashMap(*ColorPointHashMapLOD2);
UpdateColorPointHashMap(*ColorPointHashMapLOD3);
}
bool USoundscapeSubsystem::RemoveColorPointCollection(FName ColorPointCollectionName)
{
return (ColorPointCollections.Remove(ColorPointCollectionName) > 0);
}
int32 USoundscapeSubsystem::CheckColorPointDensity(FVector Location, FGameplayTag ColorPoint)
{
FVector ListenerLocation;
int32 ColorPointDensity = 0;
ESoundscapeLOD CurrentLOD = ESoundscapeLOD::LOD1;
if (UWorld* World = GetWorld())
{
if (FAudioDevice* AudioDevice = World->GetAudioDeviceRaw())
{
// Get available Listener Proxies
TArray<FListenerProxy>& ListenerProxies = AudioDevice->ListenerProxies;
// TODO: Handle Split Screen
if (ListenerProxies.Num())
{
// Get Location and Direction from Listener
FTransform& ListenerTransform = ListenerProxies[0].Transform;
ListenerLocation = ListenerTransform.GetLocation();
}
}
}
// Get relative distance from the Listener Location
FVector RelativeLocation = Location - ListenerLocation;
// If relative distance is less than LOD 1 cutoff distance, use LOD 1 Map
if (RelativeLocation.Length() < LOD1ColorPointHashDistance)
{
CurrentLOD = ESoundscapeLOD::LOD1;
ColorPointDensity = ColorPointDensity + ColorPointHashMapLOD1->NumColorPointsInCell(Location, ColorPoint);
}
else if (RelativeLocation.Length() < LOD2ColorPointHashDistance)
{
CurrentLOD = ESoundscapeLOD::LOD2;
// If relative distance is less than LOD 2 cutoff distance, use LOD 2 Map
ColorPointDensity = ColorPointDensity + ColorPointHashMapLOD2->NumColorPointsInCell(Location, ColorPoint);
}
else
{
CurrentLOD = ESoundscapeLOD::LOD3;
// If distance exceeds both cutoff distances, use LOD 3 Map
ColorPointDensity = ColorPointDensity + ColorPointHashMapLOD3->NumColorPointsInCell(Location, ColorPoint);
}
// Check Active Color Point Map
ColorPointDensity = ColorPointDensity + ActiveColorPointHashMap->NumColorPointsInCell(Location, ColorPoint);
// Check Color Point Hash Map Collections
if (ColorPointHashMapCollections.Num() > 0)
{
for (auto It = ColorPointHashMapCollections.CreateConstIterator(); It; ++It)
{
if (It)
{
USoundscapeColorPointHashMapCollection* HashMapCollection = *It;
if (HashMapCollection)
{
ColorPointDensity = ColorPointDensity + HashMapCollection->GetColorPointHashMapCollectionDensity(Location, ColorPoint, CurrentLOD);
}
}
}
}
// Return total
return ColorPointDensity;
}
void USoundscapeSubsystem::AddColorPointHashMapCollection(USoundscapeColorPointHashMapCollection* ColorPointHashMapCollection)
{
if (ColorPointHashMapCollection)
{
ColorPointHashMapCollections.Add(ColorPointHashMapCollection);
}
}
bool USoundscapeSubsystem::RemoveColorPointHashMapCollection(USoundscapeColorPointHashMapCollection* ColorPointHashMapCollection)
{
if (ColorPointHashMapCollection)
{
ColorPointHashMapCollections.RemoveSingleSwap(ColorPointHashMapCollection, EAllowShrinking::Yes);
return true;
}
return false;
}
void USoundscapeSubsystem::DrawDebugCell(FVector Location, bool bSuccess)
{
if (bDebugMode)
{
FVector ListenerLocation;
TPair<FVector, FVector> BoxDimensions;
if (UWorld* World = GetWorld())
{
if (FAudioDevice* AudioDevice = World->GetAudioDeviceRaw())
{
// Get available Listener Proxies
TArray<FListenerProxy>& ListenerProxies = AudioDevice->ListenerProxies;
// TODO: Handle Split Screen
if (ListenerProxies.Num())
{
// Get Location and Direction from Listener
FTransform& ListenerTransform = ListenerProxies[0].Transform;
ListenerLocation = ListenerTransform.GetLocation();
}
}
}
// Get relative distance from the Listener Location
FVector RelativeLocation = Location - ListenerLocation;
// If relative distance is less than LOD 1 cutoff distance, use LOD 1 Map
if (RelativeLocation.Length() < LOD1ColorPointHashDistance)
{
BoxDimensions = CalculateDebugCellDimensions(Location, ESoundscapeLOD::LOD1);
}
else if (RelativeLocation.Length() < LOD2ColorPointHashDistance)
{
BoxDimensions = CalculateDebugCellDimensions(Location, ESoundscapeLOD::LOD2);
}
else
{
BoxDimensions = CalculateDebugCellDimensions(Location, ESoundscapeLOD::LOD3);
}
FColor BoxColor = bSuccess ? FColor::Green : FColor::Red;
DrawDebugBox(GetWorld(), BoxDimensions.Key, BoxDimensions.Value, BoxColor, false, 5.0f, '\000', 5.0f);
}
}
TPair<FVector, FVector> USoundscapeSubsystem::CalculateDebugCellDimensions(FVector Location, ESoundscapeLOD SoundscapeLOD)
{
USoundscapeColorPointHashMap* LODHashMap = nullptr;
TPair<FVector, FVector> BoxResults = { FVector::ZeroVector, FVector::ZeroVector };
// If relative distance is less than LOD 1 cutoff distance, use LOD 1 Map
switch (SoundscapeLOD)
{
case ESoundscapeLOD::LOD1:
LODHashMap = ColorPointHashMapLOD1;
break;
case ESoundscapeLOD::LOD2:
LODHashMap = ColorPointHashMapLOD2;
break;
case ESoundscapeLOD::LOD3:
LODHashMap = ColorPointHashMapLOD3;
}
if (LODHashMap)
{
FVector BoxCenter = LODHashMap->GetHashCellCenterpoint(Location);
float BoxWidth = LODHashMap->GetHashCellWidth();
BoxResults.Key = BoxCenter;
BoxResults.Value = FVector(BoxWidth, BoxWidth, BoxWidth);
}
return BoxResults;
}
void USoundscapeSubsystem::AddActiveColorPoint(const USoundscapeColorPointComponent* SoundscapeColorPointComponent)
{
if (SoundscapeColorPointComponent)
{
ActiveSoundscapeColorPointComponents.AddUnique(SoundscapeColorPointComponent);
}
}
void USoundscapeSubsystem::RemoveActiveColorPoint(const USoundscapeColorPointComponent* SoundscapeColorPointComponent)
{
if (SoundscapeColorPointComponent)
{
ActiveSoundscapeColorPointComponents.RemoveSingleSwap(SoundscapeColorPointComponent, EAllowShrinking::Yes);
}
}
void USoundscapeSubsystem::UpdateState()
{
// Palettes the keep
decltype(ActivePalettes) ActivePalettesToKeep;
// Remove tags that don't match
for (const TPair<USoundscapePalette*, UActiveSoundscapePalette*> ActiveSoundscapePalettePair : ActivePalettes)
{
USoundscapePalette* SoundscapePalette = ActiveSoundscapePalettePair.Key;
UActiveSoundscapePalette* ActiveSoundscapePalette = ActiveSoundscapePalettePair.Value;
if (SoundscapePalette && ActiveSoundscapePalette)
{
if (SoundscapePalette->SoundscapePalettePlaybackConditions.IsEmpty() || SoundscapePalette->SoundscapePalettePlaybackConditions.Matches(SubsystemState))
{
ActivePalettesToKeep.Add(SoundscapePalette, ActiveSoundscapePalette);
}
else
{
ActiveSoundscapePalette->Stop();
UE_LOG(LogSoundscapeSubsystem, Verbose, TEXT("Soundscape Palette %s stopped"), *ActiveSoundscapePalette->GetFullName());
}
}
}
// Empty active palettes an append ones to keep
ActivePalettes.Empty();
ActivePalettes.Append(ActivePalettesToKeep);
// Add matching palettes
for (auto PaletteIterator = LoadedPaletteCollectionSet.CreateConstIterator(); PaletteIterator; ++PaletteIterator)
{
if (USoundscapePalette* SoundscapePalette = *PaletteIterator)
{
// Evaluate if the Soundscape Palette matches
if ((SoundscapePalette->SoundscapePalettePlaybackConditions.IsEmpty() || SoundscapePalette->SoundscapePalettePlaybackConditions.Matches(SubsystemState)) && ActivePalettes.Contains(SoundscapePalette) == false)
{
UWorld* World = GetWorld();
if (World)
{
// Create a new ActiveSoundscapeColor
UActiveSoundscapePalette* ActiveSoundscapePalette = NewObject<UActiveSoundscapePalette>(World);
//
ActiveSoundscapePalette->InitializeSettings(World, SoundscapePalette);
ActiveSoundscapePalette->Play();
UE_LOG(LogSoundscapeSubsystem, Verbose, TEXT("Soundscape Palette %s started"), *ActiveSoundscapePalette->GetFullName());
ActivePalettes.Add(SoundscapePalette, ActiveSoundscapePalette);
}
}
}
}
}
void USoundscapeSubsystem::UpdateColorPointHashMap(USoundscapeColorPointHashMap& SoundscapeColorPointHashMap)
{
// Clear out hash Map
SoundscapeColorPointHashMap.ClearHash();
// Temp Collection Array
TArray<FSoundscapeColorPointCollection> ColorPointCollectionArray;
// Fill Temp Array
ColorPointCollections.GenerateValueArray(ColorPointCollectionArray);
// Cycle through Collections
for (auto Collection = ColorPointCollectionArray.CreateConstIterator(); Collection; ++Collection)
{
// Cycle through Color Point Vector Arrays
for (auto ColorPointVectorArray = Collection->ColorPointCollection.CreateConstIterator(); ColorPointVectorArray; ++ColorPointVectorArray)
{
// Add Vector Array to Color Point Map
SoundscapeColorPointHashMap.AddColorPointArrayToHash(ColorPointVectorArray->Locations, ColorPointVectorArray->ColorPoint);
}
}
}
void USoundscapeSubsystem::UpdateActiveColorPointHashMap()
{
ActiveColorPointHashMap->ClearHash();
for (auto It = ActiveSoundscapeColorPointComponents.CreateConstIterator(); It; ++It)
{
if (It)
{
if (const USoundscapeColorPointComponent* ActiveColorPointComponent = *It)
{
FVector ColorPointLocation;
FGameplayTag ColorPointValue;
ActiveColorPointComponent->GetInfo(ColorPointValue, ColorPointLocation);
ActiveColorPointHashMap->AddColorPointToHash(ColorPointLocation, ColorPointValue);
}
}
}
// Set up for first Timer delay
float DelayTime = FMath::Max(0.2f, ActiveColorPointHashUpdateTimeSeconds);
if (UWorld* World = GetWorld())
{
World->GetTimerManager().SetTimer(ActiveColorPointUpdateTimer, this, &USoundscapeSubsystem::UpdateActiveColorPointHashMap, DelayTime);
}
}
DEFINE_LOG_CATEGORY(LogSoundscapeSubsystem);