diff --git a/.gitignore b/.gitignore index fb7793a..6a81cb9 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ build/ # Wwise .backup* .cache* +build* *.akd *.prof *.validationcache diff --git a/CMakeLists.txt b/CMakeLists.txt index 060471b..7e82643 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,9 @@ ccp_add_library(CarbonAudio SHARED src/AudEventKey.cpp src/AudEventKey_Blue.cpp src/AudGameObjResource.cpp + src/AudGeometry.cpp + src/AudGeometry_Blue.cpp + src/SpatialAudioSettings.cpp src/audio2.cpp src/AudGameObjResource_Blue.cpp src/stdafx.cpp @@ -79,6 +82,8 @@ target_sources(CarbonAudio PRIVATE src/AudStaticDataRepository.h src/AudUIPlayer.h src/AudMusicPlayer.h + src/AudGeometry.h + src/SpatialAudioSettings.h src/autoversion.h src/DebugUtilities.h src/LogBridge.h diff --git a/src/AudGeometry.cpp b/src/AudGeometry.cpp new file mode 100644 index 0000000..e73f74c --- /dev/null +++ b/src/AudGeometry.cpp @@ -0,0 +1,178 @@ +#include "stdafx.h" +#include "AudGeometry.h" +#include "Utilities.h" +#include "Vector3.h" +#include "AudManager.h" + +static CcpLogChannel_t s_ch = CCP_LOG_DEFINE_CHANNEL( "AudGeometry" ); + +namespace +{ + std::vector ConvertVertices( const std::vector& vertices ) + { + std::vector akVertices( vertices.size() ); + for( size_t i = 0; i < vertices.size(); ++i ) + { + const Vector3& v = vertices[i]; + akVertices[i] = AkVertex( static_cast( v.x ), static_cast( v.y ), static_cast( -v.z ) ); + } + return akVertices; + } + + std::vector ConvertTriangles( const std::vector& indices ) + { + size_t numTriangles = indices.size() / 3; + std::vector akTriangles( numTriangles ); + for( size_t i = 0; i < numTriangles; ++i ) + { + akTriangles[i] = AkTriangle( + static_cast( indices[i * 3 + 0] ), + static_cast( indices[i * 3 + 1] ), + static_cast( indices[i * 3 + 2] ), + 0 + ); + } + return akTriangles; + } + +} + +AudGeometry::AudGeometry( IRoot* lockobj ) +{} + +AudGeometry::~AudGeometry() +{} + +void AudGeometry::ClearAllGeometry() +{ + if( s_geometrySetRefCounts.empty() ) + { + return; + } + + if( AK::SoundEngine::IsInitialized() ) + { + for( const auto& geometrySetEntry : s_geometrySetRefCounts ) + { + AK::SpatialAudio::RemoveGeometry( geometrySetEntry.first ); + } + } + + s_geometrySetRefCounts.clear(); +} + +AkGeometryInstanceParams AudGeometry::MakeInstanceParams( + uint64_t geometrySetId, const Matrix& worldTransform ) +{ + AkGeometryInstanceParams params; + params.GeometrySetID = geometrySetId; + AkTransform transform; + RH2LH::convertTransform( worldTransform, transform ); + params.PositionAndOrientation = transform; + params.Scale = RH2LH::extractScale( worldTransform ); + return params; +} + +void AudGeometry::SetGeometry( + uint64_t geometrySetId, + uint64_t instanceId, + const Tr2AudGeometryData& geometryData, + const Matrix& worldTransform ) +{ + if( geometryData.m_vertices.empty() || geometryData.m_indices.empty() ) + { + return; + } + + if( !g_audioManager || !g_audioManager->GetSpatialAudioGeometryEnabled() ) + { + return; + } + + auto it = s_geometrySetRefCounts.find( geometrySetId ); + if( it == s_geometrySetRefCounts.end() ) + { + std::vector akVertices = ConvertVertices( geometryData.m_vertices ); + std::vector akTriangles = ConvertTriangles( geometryData.m_indices ); + + AkAcousticSurface surface; + surface.strName = "default"; + surface.textureID = AK_INVALID_UNIQUE_ID; + surface.transmissionLoss = g_audioManager->GetTransmissionLoss(); + + AkGeometryParams params; + params.Vertices = akVertices.data(); + params.NumVertices = static_cast( akVertices.size() ); + params.Triangles = akTriangles.data(); + params.NumTriangles = static_cast( akTriangles.size() ); + params.Surfaces = &surface; + params.NumSurfaces = 1; + params.EnableDiffraction = g_audioManager->GetEnableDiffraction(); + params.EnableDiffractionOnBoundaryEdges = g_audioManager->GetEnableDiffractionOnBoundaryEdges(); + + AKRESULT result = AK::SpatialAudio::SetGeometry( geometrySetId, params ); + if( result != AK_Success ) + { + CCP_LOGERR_CH( s_ch, "Failed to set geometry for set %llu, AKRESULT: %d", geometrySetId, result ); + return; + } + + s_geometrySetRefCounts[geometrySetId] = 1; + } + else + { + it->second++; + } + + AKRESULT result = AK::SpatialAudio::SetGeometryInstance( + instanceId, MakeInstanceParams( geometrySetId, worldTransform ) ); + if( result != AK_Success ) + { + CCP_LOGERR_CH( s_ch, "Failed to set geometry instance %llu (set %llu), AKRESULT: %d", + instanceId, geometrySetId, result ); + } +} + +void AudGeometry::SetGeometryTransform( + uint64_t geometrySetId, + uint64_t instanceId, + const Matrix& worldTransform ) +{ + if( !g_audioManager || !g_audioManager->GetSpatialAudioGeometryEnabled() ) + { + return; + } + + if( s_geometrySetRefCounts.find( geometrySetId ) == s_geometrySetRefCounts.end() ) + { + return; + } + + AKRESULT result = AK::SpatialAudio::SetGeometryInstance( + instanceId, MakeInstanceParams( geometrySetId, worldTransform ) ); + if( result != AK_Success ) + { + CCP_LOGERR_CH( s_ch, "Failed to update geometry instance transform for instance %llu (set %llu), AKRESULT: %d", + instanceId, geometrySetId, result ); + } +} + +void AudGeometry::RemoveGeometry( + uint64_t geometrySetId, + uint64_t instanceId ) +{ + auto it = s_geometrySetRefCounts.find( geometrySetId ); + if( it == s_geometrySetRefCounts.end() ) + { + return; + } + + AK::SpatialAudio::RemoveGeometryInstance( instanceId ); + + it->second--; + if( it->second == 0 ) + { + AK::SpatialAudio::RemoveGeometry( geometrySetId ); + s_geometrySetRefCounts.erase( it ); + } +} diff --git a/src/AudGeometry.h b/src/AudGeometry.h new file mode 100644 index 0000000..c6159ff --- /dev/null +++ b/src/AudGeometry.h @@ -0,0 +1,91 @@ +//////////////////////////////////////////////////////////// +// +// Creator: Phevos Rinis +// Creation Date: Jan 2026 +// Copyright (c) 2026 CCP Games +// + +#pragma once + + +#include +#include + +#include + + +/** + * @brief Registers meshes from Trinity as Spatial Audio geometry sets and manages their lifecycle. + * + * Implements the ITr2AudGeometry interface to register meshes to + * AK::SpatialAudio as geeometry sets. A geometry set is a set of vertices, triangles, + * and acoustic surfaces (see AkGeometryParams). Each geometry instance represents a unique + * placement of a geometry set in the world with a transform — position, + * orientation, and scale. + */ +BLUE_CLASS(AudGeometry) : + public ITr2AudGeometry + +{ +public: + AudGeometry(IRoot* lockobj = NULL); + virtual ~AudGeometry(); + + EXPOSE_TO_BLUE(); + + + // ITr2AudGeometry interface + + /** + * @brief Registers a geometry set instance and places it in the world. + * + * @param geometrySetId Shared geometry set identifier. + * @param instanceId Unique geometry instance identifier. + * @param geometryData Triangle mesh data. + * @param worldTransform World position, orientation and scale. + * + */ + void SetGeometry( + uint64_t geometrySetId, + uint64_t instanceId, + const Tr2AudGeometryData& geometryData, + const Matrix& worldTransform) override; + + /** + * @brief Updates the world transform of an existing geometry instance + * + * @param geometrySetId Geometry set the instance references. + * @param instanceId Geometry instance to update. + * @param worldTransform New world-space position, orientation and scale. + * + */ + void SetGeometryTransform( + uint64_t geometrySetId, + uint64_t instanceId, + const Matrix& worldTransform) override; + + /** + * @brief Removes a geometry instance and releases the geometry set when + * its reference count reaches zero. + * + * @param geometrySetId Geometry set to dereference. + * @param instanceId Geometry instance to remove. + * + */ + void RemoveGeometry( + uint64_t geometrySetId, + uint64_t instanceId) override; + + /// Removes all registered geometry sets from the Wwise engine. + static void ClearAllGeometry(); + +private: + /// Builds AkGeometryInstanceParams from a geometry set ID and world transform. + static AkGeometryInstanceParams MakeInstanceParams( + uint64_t geometrySetId, const Matrix& worldTransform ); + + /// Tracks how many active instances reference each geometry set. + inline static std::unordered_map s_geometrySetRefCounts; +}; + +TYPEDEF_BLUECLASS( AudGeometry ); diff --git a/src/AudGeometry_Blue.cpp b/src/AudGeometry_Blue.cpp new file mode 100644 index 0000000..4eb19a1 --- /dev/null +++ b/src/AudGeometry_Blue.cpp @@ -0,0 +1,12 @@ +#include "stdafx.h" +#include "AudGeometry.h" + +BLUE_DEFINE( AudGeometry ); +BLUE_DEFINE_INTERFACE( ITr2AudGeometry ); + +const Be::ClassInfo* AudGeometry::ExposeToBlue() +{ + EXPOSURE_BEGIN( AudGeometry, "Audio geometry for Wwise Spatial Audio occlusion/diffraction" ) + MAP_INTERFACE( ITr2AudGeometry ) + EXPOSURE_END() +} diff --git a/src/AudListener.cpp b/src/AudListener.cpp index 395d260..bbe8697 100644 --- a/src/AudListener.cpp +++ b/src/AudListener.cpp @@ -3,6 +3,7 @@ #include "stdafx.h" #include "AudListener.h" +#include "AudManager.h" #include "Vector3.h" #include "Utilities.h" @@ -15,8 +16,12 @@ AudListener::AudListener( IRoot* lockobj ) : AudGameObjResource( LISTENER_GAME_O AudListener::~AudListener() { - AK::SoundEngine::RemoveDefaultListener(m_ID); - AK::SoundEngine::UnregisterGameObj(m_ID); + if( g_audioManager != nullptr && g_audioManager->GetSpatialAudioGeometryEnabled() ) + { + AK::SpatialAudio::UnregisterListener( m_ID ); + } + AK::SoundEngine::RemoveDefaultListener( m_ID ); + AK::SoundEngine::UnregisterGameObj( m_ID ); } void AudListener::RegisterWwiseObject() @@ -27,6 +32,13 @@ void AudListener::RegisterWwiseObject() { AK::SoundEngine::RegisterGameObj(m_ID, m_name.c_str()); AK::SoundEngine::AddDefaultListener(m_ID); + + // Register listener for occlusion/diffraction processing + if( g_audioManager != nullptr && g_audioManager->GetSpatialAudioGeometryEnabled() ) + { + AK::SpatialAudio::RegisterListener( m_ID ); + } + m_gameObjRegistered = true; } } @@ -48,7 +60,7 @@ int AudListener::SetPositionHelper( const Vector3& front, const Vector3& top, co Vector3 correctFront = Normalize( front ); Vector3 correctUp = Normalize( top ); correctUp = Normalize( Cross( Cross( correctFront, correctUp ), correctFront ) ); - tmp.Set( MakeAkVector(position), MakeAkVector(correctFront), MakeAkVector(correctUp) ); + tmp.Set( MakeAkVector( position ), MakeAkVector( correctFront ), MakeAkVector( correctUp ) ); // all vectors come in RH, but WWISE is LH, so convert AkSoundPosition soundPosLH; @@ -58,4 +70,4 @@ int AudListener::SetPositionHelper( const Vector3& front, const Vector3& top, co } } return AK_Success; -} \ No newline at end of file +} diff --git a/src/AudManager.cpp b/src/AudManager.cpp index e0a0836..1f9ef8a 100644 --- a/src/AudManager.cpp +++ b/src/AudManager.cpp @@ -26,6 +26,7 @@ #include "AudActionLog.h" #include "AudEmitter.h" +#include "AudGeometry.h" #include "AudSettings.h" #include "AudStaticDataRepository.h" #include "LogBridge.h" @@ -65,6 +66,7 @@ AudManager::AudManager( IRoot* lockobj ) : m_asyncOpen( true ), m_log(), m_spatialAudioEnabled( true ), + m_spatialAudioGeometryInitialized( false ), m_moniteredParametersMapMutex( "AudManager", "m_monitoredParametersMapMutex" ), m_soundBankMutex( "AudManager", "m_soundBankMutex" ), m_callbackGameObjectsMutex( "AudManager", "m_callbackGameObjectsMutex" ), @@ -73,12 +75,14 @@ AudManager::AudManager( IRoot* lockobj ) : { // Initialize sound prioritization system m_soundPrioritization = new SoundPrioritization(); + m_spatialAudioSettings = new SpatialAudioSettings(); } AudManager::~AudManager() { // Clean up sound prioritization system delete m_soundPrioritization; + delete m_spatialAudioSettings; if( g_audioInitialized ) { @@ -123,6 +127,8 @@ AkBankID AudManager::ComputeWwiseHashForSoundBank( const std::wstring& soundBank bool AudManager::Init() { + m_spatialAudioGeometryInitialized = false; + if( g_staticDataRepository == nullptr || !g_staticDataRepository->IsInitialized() ) { CCP_LOGERR( "The static data repository in audio2 has not been generated and needs to exist for audio2 " @@ -141,6 +147,15 @@ bool AudManager::Init() return false; } + if( m_spatialAudioSettings->GetSpatialAudioGeometryEnabled() ) + { + if( !InitSpatialAudioGeometry() ) + { + CCP_LOGERR( "Failed to initialize Spatial Audio Geometry" ); + return false; + } + } + #ifndef AK_OPTIMIZED if( !InitCommunication() ) { @@ -177,6 +192,7 @@ void AudManager::Terminate() // Terminate the Memory Manager AK::MemoryMgr::Term(); + m_spatialAudioGeometryInitialized = false; g_audioInitialized = false; } @@ -387,6 +403,27 @@ bool AudManager::InitSound() return true; } +bool AudManager::InitSpatialAudioGeometry() +{ + if( m_spatialAudioGeometryInitialized ) + { + return true; + } + + AkSpatialAudioInitSettings spatialSettings; + m_spatialAudioSettings->PopulateInitSettings( spatialSettings ); + + if( AK::SpatialAudio::Init( spatialSettings ) != AK_Success ) + { + CCP_LOGERR( "Failed to initialize Wwise Spatial Audio for geometry processing" ); + return false; + } + + m_spatialAudioGeometryInitialized = true; + CCP_LOG_CH( s_ch, "Wwise Spatial Audio Geometry initialized" ); + return true; +} + bool AudManager::SetGlobalRTPC( const std::wstring& rtpcName, float value ) { if( g_audioInitialized && !g_shuttingDown) @@ -416,10 +453,44 @@ bool AudManager::SetState( const std::wstring& stateGroup, const std::wstring& s return false; } -//----------------------------------------------------- -// Description: -// Signals whether Carbon Audio supports spatial audio features on this operating system. -//----------------------------------------------------- +bool AudManager::GetSpatialAudioGeometryEnabled() const +{ + return m_spatialAudioSettings->GetSpatialAudioGeometryEnabled(); +} + +void AudManager::SetSpatialAudioGeometryEnabled( bool enabled ) +{ + const bool wasEnabled = GetSpatialAudioGeometryEnabled(); + if( wasEnabled == enabled ) + { + return; + } + + if( !g_audioInitialized ) + { + m_spatialAudioSettings->SetSpatialAudioGeometryEnabled( enabled ); + return; + } + + if( !enabled ) + { + m_spatialAudioSettings->SetSpatialAudioGeometryEnabled( false ); + AudGeometry::ClearAllGeometry(); + CCP_LOG_CH( s_ch, "Spatial audio geometry disabled." ); + } + else + { + if( !InitSpatialAudioGeometry() ) + { + CCP_LOGERR_CH( s_ch, "Spatial audio geometry failed to initialize." ); + return; + } + + m_spatialAudioSettings->SetSpatialAudioGeometryEnabled( true ); + CCP_LOG_CH( s_ch, "Spatial audio geometry enabled." ); + } +} + const bool AudManager::SpatialAudioIsSupported() { return s_systemSupportsSpatialAudio; @@ -674,6 +745,7 @@ void AudManager::Disable() #ifndef AK_OPTIMIZED AK::SoundEngine::UnregisterResourceMonitorCallback(ResourceMonitorCallback); #endif + AudGeometry::ClearAllGeometry(); Terminate(); g_audioEnabled = false; @@ -1284,4 +1356,4 @@ void AudManager::ResourceMonitorCallback( const AkResourceMonitorDataSummary* da CCP_STATS_SET( totalVoices, dataSummary->totalVoices ); CCP_STATS_SET( nbActiveEvents, dataSummary->nbActiveEvents ); } -#endif \ No newline at end of file +#endif diff --git a/src/AudManager.h b/src/AudManager.h index ca15233..ca44485 100644 --- a/src/AudManager.h +++ b/src/AudManager.h @@ -8,6 +8,7 @@ #include "AudSettings.h" #include "AudListener.h" #include "SoundPrioritization.h" +#include "SpatialAudioSettings.h" #include "LowLevelIO/LowLevelIOHook.h" #include #include @@ -106,6 +107,10 @@ BLUE_CLASS( AudManager ) : bool SetGlobalRTPC( const std::wstring& rtpcName, float value ); // Set a global state in Wwise. bool SetState( const std::wstring& stateGroup, const std::wstring& stateName ); + // Returns whether spatial audio geometry is enabled. + bool GetSpatialAudioGeometryEnabled() const; + // Enables or disables spatial audio geometry. + void SetSpatialAudioGeometryEnabled( bool enabled ); // Can be called to see if the current platform supports spatial audio. const bool SpatialAudioIsSupported(); // Stop all currently playing sounds on all game objects. @@ -175,6 +180,8 @@ BLUE_CLASS( AudManager ) : bool InitMusic(); // Initializes Wwise's sound engine. bool InitSound(); + // Initializes Wwise's Spatial Audio for geometry-based occlusion and diffraction. + bool InitSpatialAudioGeometry(); // Tick handler void Process(); // Registers audio2 for the tick handler. @@ -202,6 +209,8 @@ BLUE_CLASS( AudManager ) : bool m_asyncOpen; // Signals whether Carbon Audio's spatial audio features are enabled. If the user currently doesn't have an active spatial audio endpoint then output will still be in stereo. bool m_spatialAudioEnabled; + // Tracks whether Wwise Spatial Audio geometry has been initialized in the current audio-engine lifetime. + bool m_spatialAudioGeometryInitialized; mutable bool m_audioCullingEnabled; std::map m_soundBankInfoMap; @@ -216,6 +225,7 @@ BLUE_CLASS( AudManager ) : CcpMutex m_moniteredParametersMapMutex; SoundPrioritization* m_soundPrioritization; + SpatialAudioSettings* m_spatialAudioSettings; // Map of game objects, used to guard Wwise callbacks std::unordered_map m_callbackGameObjects; @@ -281,6 +291,60 @@ BLUE_CLASS( AudManager ) : #undef DELEGATE_GETTER #undef DELEGATE_SETTER + +public: + //----------------------------------------------------- + // Description: + // Delegate macros to forward getter/setter calls to + // the SpatialAudioSettings instance. + //----------------------------------------------------- + +#define DELEGATE_SA_GETTER( ReturnType, MethodName ) \ + ReturnType MethodName() const \ + { \ + return m_spatialAudioSettings->MethodName(); \ + } + +#define DELEGATE_SA_SETTER( ParamType, MethodName ) \ + void MethodName( ParamType value ) \ + { \ + m_spatialAudioSettings->MethodName( value ); \ + } + + // Getters + DELEGATE_SA_GETTER( float, GetMovementThreshold ) + DELEGATE_SA_GETTER( int, GetNumberOfPrimaryRays ) + DELEGATE_SA_GETTER( int, GetMaxReflectionOrder ) + DELEGATE_SA_GETTER( int, GetMaxDiffractionOrder ) + DELEGATE_SA_GETTER( int, GetMaxEmitterRoomAuxSends ) + DELEGATE_SA_GETTER( int, GetDiffractionOnReflectionsOrder ) + DELEGATE_SA_GETTER( float, GetMaxPathLength ) + DELEGATE_SA_GETTER( float, GetCPULimitPercentage ) + DELEGATE_SA_GETTER( int, GetLoadBalancingSpread ) + DELEGATE_SA_GETTER( bool, GetEnableDiffractionAndTransmission ) + DELEGATE_SA_GETTER( bool, GetCalcEmitterVirtualPosition ) + DELEGATE_SA_GETTER( float, GetTransmissionLoss ) + DELEGATE_SA_GETTER( bool, GetEnableDiffraction ) + DELEGATE_SA_GETTER( bool, GetEnableDiffractionOnBoundaryEdges ) + + // Setters + DELEGATE_SA_SETTER( float, SetMovementThreshold ) + DELEGATE_SA_SETTER( int, SetNumberOfPrimaryRays ) + DELEGATE_SA_SETTER( int, SetMaxReflectionOrder ) + DELEGATE_SA_SETTER( int, SetMaxDiffractionOrder ) + DELEGATE_SA_SETTER( int, SetMaxEmitterRoomAuxSends ) + DELEGATE_SA_SETTER( int, SetDiffractionOnReflectionsOrder ) + DELEGATE_SA_SETTER( float, SetMaxPathLength ) + DELEGATE_SA_SETTER( float, SetCPULimitPercentage ) + DELEGATE_SA_SETTER( int, SetLoadBalancingSpread ) + DELEGATE_SA_SETTER( bool, SetEnableDiffractionAndTransmission ) + DELEGATE_SA_SETTER( bool, SetCalcEmitterVirtualPosition ) + DELEGATE_SA_SETTER( float, SetTransmissionLoss ) + DELEGATE_SA_SETTER( bool, SetEnableDiffraction ) + DELEGATE_SA_SETTER( bool, SetEnableDiffractionOnBoundaryEdges ) + +#undef DELEGATE_SA_GETTER +#undef DELEGATE_SA_SETTER }; TYPEDEF_BLUECLASS( AudManager ); diff --git a/src/AudManager_Blue.cpp b/src/AudManager_Blue.cpp index 1577b5a..a80665f 100644 --- a/src/AudManager_Blue.cpp +++ b/src/AudManager_Blue.cpp @@ -24,6 +24,23 @@ const Be::ClassInfo* AudManager::ExposeToBlue() MAP_PROPERTY( "waitingOneShotWeight", GetWaitingOneShotWeight, SetWaitingOneShotWeight, "The weight applied to a game object if there is a one shot sound waiting to play.") MAP_PROPERTY( "visibleWeight", GetVisibleWeight, SetVisibleWeight, "The weight applied to a game object if it is visible to the listener.") MAP_PROPERTY( "playing2DWeight", GetPlaying2DWeight, SetPlaying2DWeight, "The weight applied to a game object if it is currently playing a 2D sound.") + + // Spatial audio geometry settings + MAP_PROPERTY( "spatialAudioGeometryEnabled", GetSpatialAudioGeometryEnabled, SetSpatialAudioGeometryEnabled, "Enable or disable spatial audio geometry.") + MAP_PROPERTY( "movementThreshold", GetMovementThreshold, SetMovementThreshold, "Distance an emitter or listener must move to trigger a re-validation of reflections/diffraction.") + MAP_PROPERTY( "numberOfPrimaryRays", GetNumberOfPrimaryRays, SetNumberOfPrimaryRays, "Number of primary rays used in the ray tracing engine. More rays = better quality but higher CPU.") + MAP_PROPERTY( "maxReflectionOrder", GetMaxReflectionOrder, SetMaxReflectionOrder, "Maximum reflection order [1-4] - number of bounces in a reflection path.") + MAP_PROPERTY( "maxDiffractionOrder", GetMaxDiffractionOrder, SetMaxDiffractionOrder, "Maximum diffraction order [1-8] - number of bends in a diffraction path. Set to 0 to disable diffraction.") + MAP_PROPERTY( "maxEmitterRoomAuxSends", GetMaxEmitterRoomAuxSends, SetMaxEmitterRoomAuxSends, "Maximum number of game-defined auxiliary sends from a single emitter. Set to 0 to disable the limit.") + MAP_PROPERTY( "diffractionOnReflectionsOrder", GetDiffractionOnReflectionsOrder, SetDiffractionOnReflectionsOrder, "Maximum diffraction points at each end of a reflection path. Set to 0 to disable diffraction on reflections.") + MAP_PROPERTY( "maxPathLength", GetMaxPathLength, SetMaxPathLength, "Maximum total length of a path composed of segments. Higher values compute longer paths but increase CPU cost.") + MAP_PROPERTY( "cpuLimitPercentage", GetCPULimitPercentage, SetCPULimitPercentage, "Targeted computation time for ray tracing as a percentage [0-100] of the audio frame. 0 = no limit.") + MAP_PROPERTY( "loadBalancingSpread", GetLoadBalancingSpread, SetLoadBalancingSpread, "Spread path computation over N frames [1..]. 1 = no load balancing.") + MAP_PROPERTY( "enableDiffractionAndTransmission", GetEnableDiffractionAndTransmission, SetEnableDiffractionAndTransmission, "Enable geometric diffraction and transmission path computation.") + MAP_PROPERTY( "calcEmitterVirtualPosition", GetCalcEmitterVirtualPosition, SetCalcEmitterVirtualPosition, "Calculate virtual position for emitters diffracted through portals or around geometry.") + MAP_PROPERTY( "transmissionLoss", GetTransmissionLoss, SetTransmissionLoss, "Per-mesh setting: transmission loss [0.0-1.0] applied to geometry surfaces when meshes are registered.") + MAP_PROPERTY( "enableDiffraction", GetEnableDiffraction, SetEnableDiffraction, "Per-mesh setting: enable or disable geometric diffraction on mesh geometry.") + MAP_PROPERTY( "enableDiffractionOnBoundaryEdges", GetEnableDiffractionOnBoundaryEdges, SetEnableDiffractionOnBoundaryEdges, "Per-mesh setting: switch to enable or disable geometric diffraction on boundary edges for this mesh.") MAP_METHOD_AND_WRAP ( @@ -204,4 +221,4 @@ const Be::ClassInfo* AudManager::ExposeToBlue() ":return: True if the profiler is capturing, False otherwise." ) EXPOSURE_END() -} \ No newline at end of file +} diff --git a/src/SpatialAudioSettings.cpp b/src/SpatialAudioSettings.cpp new file mode 100644 index 0000000..d1332ff --- /dev/null +++ b/src/SpatialAudioSettings.cpp @@ -0,0 +1,82 @@ +#include "stdafx.h" +#include "SpatialAudioSettings.h" +#include + +SpatialAudioSettings::SpatialAudioSettings() + : m_spatialAudioGeometryEnabled( false ) + , m_movementThreshold( 100.0f ) + , m_numberOfPrimaryRays( 35 ) + , m_maxReflectionOrder( 0 ) + , m_maxDiffractionOrder( 4 ) + , m_maxEmitterRoomAuxSends( 0 ) + , m_diffractionOnReflectionsOrder( 0 ) + , m_maxPathLength( 1000.0f ) + , m_cpuLimitPercentage( 20.0f ) + , m_loadBalancingSpread( 1 ) + , m_enableDiffractionAndTransmission( true ) + , m_calcEmitterVirtualPosition( true ) + , m_transmissionLoss( 0.7f ) + , m_enableDiffraction( true ) + , m_enableDiffractionOnBoundaryEdges( true ) +{ +} + +bool SpatialAudioSettings::GetSpatialAudioGeometryEnabled() const { return m_spatialAudioGeometryEnabled; } +void SpatialAudioSettings::SetSpatialAudioGeometryEnabled( bool value ) { m_spatialAudioGeometryEnabled = value; } + +float SpatialAudioSettings::GetMovementThreshold() const { return m_movementThreshold; } +void SpatialAudioSettings::SetMovementThreshold( float value ) { m_movementThreshold = value; } + +int SpatialAudioSettings::GetNumberOfPrimaryRays() const { return m_numberOfPrimaryRays; } +void SpatialAudioSettings::SetNumberOfPrimaryRays( int value ) { m_numberOfPrimaryRays = value; } + +int SpatialAudioSettings::GetMaxReflectionOrder() const { return m_maxReflectionOrder; } +void SpatialAudioSettings::SetMaxReflectionOrder( int value ) { m_maxReflectionOrder = value; } + +int SpatialAudioSettings::GetMaxDiffractionOrder() const { return m_maxDiffractionOrder; } +void SpatialAudioSettings::SetMaxDiffractionOrder( int value ) { m_maxDiffractionOrder = value; } + +int SpatialAudioSettings::GetMaxEmitterRoomAuxSends() const { return m_maxEmitterRoomAuxSends; } +void SpatialAudioSettings::SetMaxEmitterRoomAuxSends( int value ) { m_maxEmitterRoomAuxSends = value; } + +int SpatialAudioSettings::GetDiffractionOnReflectionsOrder() const { return m_diffractionOnReflectionsOrder; } +void SpatialAudioSettings::SetDiffractionOnReflectionsOrder( int value ) { m_diffractionOnReflectionsOrder = value; } + +float SpatialAudioSettings::GetMaxPathLength() const { return m_maxPathLength; } +void SpatialAudioSettings::SetMaxPathLength( float value ) { m_maxPathLength = value; } + +float SpatialAudioSettings::GetCPULimitPercentage() const { return m_cpuLimitPercentage; } +void SpatialAudioSettings::SetCPULimitPercentage( float value ) { m_cpuLimitPercentage = value; } + +int SpatialAudioSettings::GetLoadBalancingSpread() const { return m_loadBalancingSpread; } +void SpatialAudioSettings::SetLoadBalancingSpread( int value ) { m_loadBalancingSpread = value; } + +bool SpatialAudioSettings::GetEnableDiffractionAndTransmission() const { return m_enableDiffractionAndTransmission; } +void SpatialAudioSettings::SetEnableDiffractionAndTransmission( bool value ) { m_enableDiffractionAndTransmission = value; } + +bool SpatialAudioSettings::GetCalcEmitterVirtualPosition() const { return m_calcEmitterVirtualPosition; } +void SpatialAudioSettings::SetCalcEmitterVirtualPosition( bool value ) { m_calcEmitterVirtualPosition = value; } + +float SpatialAudioSettings::GetTransmissionLoss() const { return m_transmissionLoss; } +void SpatialAudioSettings::SetTransmissionLoss( float value ) { m_transmissionLoss = std::max( 0.0f, std::min( 1.0f, value ) ); } + +bool SpatialAudioSettings::GetEnableDiffraction() const { return m_enableDiffraction; } +void SpatialAudioSettings::SetEnableDiffraction( bool value ) { m_enableDiffraction = value; } + +bool SpatialAudioSettings::GetEnableDiffractionOnBoundaryEdges() const { return m_enableDiffractionOnBoundaryEdges; } +void SpatialAudioSettings::SetEnableDiffractionOnBoundaryEdges( bool value ) { m_enableDiffractionOnBoundaryEdges = value; } + +void SpatialAudioSettings::PopulateInitSettings( AkSpatialAudioInitSettings& out ) const +{ + out.fMovementThreshold = m_movementThreshold; + out.uNumberOfPrimaryRays = m_numberOfPrimaryRays; + out.uMaxReflectionOrder = m_maxReflectionOrder; + out.uMaxDiffractionOrder = m_maxDiffractionOrder; + out.uMaxEmitterRoomAuxSends = m_maxEmitterRoomAuxSends; + out.uDiffractionOnReflectionsOrder = m_diffractionOnReflectionsOrder; + out.fMaxPathLength = m_maxPathLength; + out.fCPULimitPercentage = m_cpuLimitPercentage; + out.uLoadBalancingSpread = m_loadBalancingSpread; + out.bEnableGeometricDiffractionAndTransmission = m_enableDiffractionAndTransmission; + out.bCalcEmitterVirtualPosition = m_calcEmitterVirtualPosition; +} diff --git a/src/SpatialAudioSettings.h b/src/SpatialAudioSettings.h new file mode 100644 index 0000000..ab0d367 --- /dev/null +++ b/src/SpatialAudioSettings.h @@ -0,0 +1,211 @@ +//////////////////////////////////////////////////////////// +// +// Creator: Phevos Rinis +// Creation Date: Mar 2026 +// Copyright (c) 2026 CCP Games +// + +#pragma once + +struct AkSpatialAudioInitSettings; + +/** + * @brief A wrapper with configuration settings for Wwise Spatial Audio initialization. + * + */ +class SpatialAudioSettings +{ +public: + SpatialAudioSettings(); + + /** + * @brief Controls whether geometry based spatial audio processing is enabled. + */ + bool GetSpatialAudioGeometryEnabled() const; + void SetSpatialAudioGeometryEnabled( bool value ); + + /** + * @brief Amount that an emitter or listener has to move to trigger a validation of reflections/diffraction. + * + * Larger values can reduce the CPU load at the cost of reduced accuracy. + * Note that the ray tracing itself is not affected by this value. + * Rays are cast each time a Spatial Audio update is executed. + */ + float GetMovementThreshold() const; + void SetMovementThreshold( float value ); + + /** + * @brief The number of primary rays used in the ray tracing engine. + * + * A larger number of rays will increase the chances of finding reflection and diffraction paths, + * but will result in higher CPU usage. When CPU limit is active + * (see @c fCPULimitPercentage), this setting represents the maximum + * allowed number of primary rays. + */ + int GetNumberOfPrimaryRays() const; + void SetNumberOfPrimaryRays( int value ); + + /** + * @brief Maximum reflection order [1, 4] - the number of 'bounces' in a reflection path. + * + * A high reflection order renders more details at the expense of higher CPU usage. + */ + int GetMaxReflectionOrder() const; + void SetMaxReflectionOrder( int value ); + + /** + * @brief Maximum diffraction order [1, 8] - the number of 'bends' in a diffraction path. + * + * A high diffraction order accommodates more complex geometry at the expense of higher CPU usage. + * Diffraction must be enabled on the geometry to find diffraction paths + * (refer to @c AkGeometryParams). Set to 0 to disable diffraction on all geometry. + * This parameter limits the recursion depth of diffraction rays cast from the listener + * to scan the environment, and also the depth of the diffraction search to find paths + * between emitter and listener. + * To optimize CPU usage, set it to the maximum number of edges you expect the obstructing + * geometry to traverse. For example, if box-shaped geometry is used exclusively, and only + * a single box is expected between an emitter and the listener, limiting @c uMaxDiffractionOrder + * to 2 may be sufficient. + * A diffraction path search starts from the listener, so when the maximum diffraction order + * is exceeded, the remaining geometry between the end of the path and the emitter is ignored. + * In such case, where the search is terminated before reaching the emitter, the diffraction + * coefficient will be underestimated. It is calculated from a partial path, ignoring any + * remaining geometry. + */ + int GetMaxDiffractionOrder() const; + void SetMaxDiffractionOrder( int value ); + + /** + * @brief The maximum number of game-defined auxiliary sends that can originate from a single emitter. + * + * An emitter can send to its own room, and to all adjacent rooms if the emitter and listener + * are in the same room. If a limit is set, the most prominent sends are kept, based on spread + * to the adjacent portal from the emitter's perspective. + * Set to 1 to only allow emitters to send directly to their current room, and to the room + * a listener is transitioning to if inside a portal. Set to 0 to disable the limit. + */ + int GetMaxEmitterRoomAuxSends() const; + void SetMaxEmitterRoomAuxSends( int value ); + + /** + * @brief The maximum possible number of diffraction points at each end of a reflection path. + * + * Diffraction on reflection allows reflections to fade in and out smoothly as the listener + * or emitter moves in and out of the reflection's shadow zone. + * When greater than zero, diffraction rays are sent from the listener to search for reflections + * around one or more corners from the listener. + * Diffraction must be enabled on the geometry to find diffracted reflections + * (refer to @c AkGeometryParams). Set to 0 to disable diffraction on reflections. + * To allow reflections to propagate through portals without being cut off, + * set @c uDiffractionOnReflectionsOrder to 2 or greater. + */ + int GetDiffractionOnReflectionsOrder() const; + void SetDiffractionOnReflectionsOrder( int value ); + + /** + * @brief The total length of a path composed of a sequence of segments (or rays) cannot exceed + * the defined maximum path length. + * + * High values compute longer paths but increase the CPU cost. + * Each individual sound is also affected by its maximum attenuation distance, specified + * in the Authoring tool. Reflection or diffraction paths, calculated inside Spatial Audio, + * will never exceed a sound's maximum attenuation distance. + * Note, however, that attenuation is considered infinite if the furthest point is above + * the audibility threshold. + */ + float GetMaxPathLength() const; + void SetMaxPathLength( float value ); + + /** + * @brief Defines the targeted computation time allocated for the ray tracing engine. + * + * Defined as a percentage [0, 100] of the current audio frame. + * The ray tracing engine dynamically adapts the number of primary rays to target + * the specified computation time value. In all circumstances, the computed number + * of primary rays cannot exceed the number of primary rays specified by + * @c uNumberOfPrimaryRays. + * A value of 0 indicates no target has been set. In this case, the number of primary + * rays is fixed and is set by @c uNumberOfPrimaryRays. + */ + float GetCPULimitPercentage() const; + void SetCPULimitPercentage( float value ); + + /** + * @brief Spread the computation of paths on uLoadBalancingSpread frames [1, ..]. + * + * When uLoadBalancingSpread is set to 1, no load balancing is done. + * Values greater than 1 indicate the computation of paths will be spread + * on this number of frames. + */ + int GetLoadBalancingSpread() const; + void SetLoadBalancingSpread( int value ); + + /** + * @brief Enable computation of geometric diffraction and transmission paths for all sources + * that have the "Enable Diffraction and Transmission" box checked in the Positioning + * tab of the Wwise Property Editor. + * + * This flag enables sound paths around (diffraction) and through (transmission) geometry + * (see @c AK::SpatialAudio::SetGeometry). + * Setting @c bEnableGeometricDiffractionAndTransmission to false implies that geometry + * is only to be used for reflection calculation. + * Diffraction edges must be enabled on geometry for diffraction calculation + * (see @c AkGeometryParams). + * If @c bEnableGeometricDiffractionAndTransmission is false but a sound has + * "Enable Diffraction and Transmission" selected in the Positioning tab of the authoring tool, + * the sound will diffract through portals but will pass through geometry as if it is not there. + * One would typically disable this setting in the case that the game intends to perform its own + * obstruction calculation, but geometry is still passed to spatial audio for reflection calculation. + */ + bool GetEnableDiffractionAndTransmission() const; + void SetEnableDiffractionAndTransmission( bool value ); + + /** + * @brief An emitter that is diffracted through a portal or around geometry will have its + * apparent or virtual position calculated by Wwise Spatial Audio and passed on to + * the sound engine. + */ + bool GetCalcEmitterVirtualPosition() const; + void SetCalcEmitterVirtualPosition( bool value ); + + /** + * @brief Transmission loss [0.0-1.0] applied to geometry surfaces when meshes are registered. + */ + float GetTransmissionLoss() const; + void SetTransmissionLoss( float value ); + + /** + * @brief Switch to enable or disable geometric diffraction for this Geometry. + */ + bool GetEnableDiffraction() const; + void SetEnableDiffraction( bool value ); + + /** + * @brief Switch to enable or disable geometric diffraction on boundary edges for this mesh. + * Boundary edges are edges that are connected to only one triangle. + */ + bool GetEnableDiffractionOnBoundaryEdges() const; + void SetEnableDiffractionOnBoundaryEdges( bool value ); + + /** + * @brief Populates an AkSpatialAudioInitSettings struct from the current settings. + */ + void PopulateInitSettings( AkSpatialAudioInitSettings& out ) const; + +private: + bool m_spatialAudioGeometryEnabled; + float m_movementThreshold; + int m_numberOfPrimaryRays; + int m_maxReflectionOrder; + int m_maxDiffractionOrder; + int m_maxEmitterRoomAuxSends; + int m_diffractionOnReflectionsOrder; + float m_maxPathLength; + float m_cpuLimitPercentage; + int m_loadBalancingSpread; + bool m_enableDiffractionAndTransmission; + bool m_calcEmitterVirtualPosition; + float m_transmissionLoss; + bool m_enableDiffraction; + bool m_enableDiffractionOnBoundaryEdges; +}; diff --git a/src/Utilities.h b/src/Utilities.h index 7ae1540..ca0687b 100644 --- a/src/Utilities.h +++ b/src/Utilities.h @@ -4,6 +4,8 @@ #ifndef _AUD_UTILITIES_H_ #define _AUD_UTILITIES_H_ +#include + // Inherit this privatly to inhibit copying class NoCopy { @@ -27,8 +29,9 @@ class RH2LH front.X *= -1.f; front.Y *= -1.f; + + pos.Z *= -1.f; top.Z *= -1.f; - pos.Z *= -1.f; listenerLH->Set(pos, front, top); } @@ -38,13 +41,66 @@ class RH2LH AkVector front = emitterLH->OrientationFront(); AkVector top = emitterLH->OrientationTop(); AkVector64 pos = emitterLH->Position(); - + pos.Z *= -1.f; front.Z *= -1.f; top.Z *= -1.f; emitterLH->Set(pos, front, top); } + + static void convertTransform( const Matrix& matrix, AkTransform& out ) + { + constexpr float kEpsilon = 1e-10f; + + Ak3DVector32 position( matrix._41, matrix._42, -matrix._43 ); + if( !position.IsFinite() ) + position = Ak3DVector32( 0.0f, 0.0f, 0.0f ); + + Ak3DVector32 front( -matrix._31, -matrix._32, matrix._33 ); + if( !front.IsFinite() || front.LengthSquared() <= kEpsilon ) + front = Ak3DVector32( 0.0f, 0.0f, 1.0f ); + else + front.Normalize(); + + Ak3DVector32 up( matrix._21, matrix._22, -matrix._23 ); + if( !up.IsFinite() || up.LengthSquared() <= kEpsilon ) + up = Ak3DVector32( 0.0f, 1.0f, 0.0f ); + else + up.Normalize(); + + Ak3DVector32 right = up.Cross( front ); + if( right.LengthSquared() <= kEpsilon ) + { + up = ( std::fabs( front.Y ) < 0.9f ) + ? Ak3DVector32( 0.0f, 1.0f, 0.0f ) + : Ak3DVector32( 0.0f, 0.0f, 1.0f ); + right = up.Cross( front ); + } + right.Normalize(); + up = front.Cross( right ); + up.Normalize(); + + out.SetPosition( position ); + out.SetOrientation( front, up ); + } + + static AkVector extractScale( const Matrix& matrix ) + { + auto axisLength = []( float x, float y, float z ) -> float + { + const Ak3DVector32 axis( x, y, z ); + if( !axis.IsFinite() || axis.LengthSquared() <= 1e-10f ) + return 1.0f; + return axis.Length(); + }; + + AkVector scale; + scale.X = axisLength( matrix._11, matrix._12, matrix._13 ); + scale.Y = axisLength( matrix._21, matrix._22, matrix._23 ); + scale.Z = axisLength( matrix._31, matrix._32, matrix._33 ); + return scale; + } }; class StringUtils diff --git a/src/stdafx.h b/src/stdafx.h index 7bc0899..08f5d6a 100644 --- a/src/stdafx.h +++ b/src/stdafx.h @@ -43,6 +43,8 @@ #include #include +#include + #ifndef AK_OPTIMIZED #include #endif